以太坊硬件钱包原理_以太坊区块链怎么挣钱

2022-09-27 10:04:32 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君

钱包的本质

私钥就是资金的所有和使用权。钱包控制对以太币的访问、管理私钥和地址、跟踪账户的余额。

私钥通过 ECDSA(椭圆曲线签名算法)推导出公钥,继而经过 Keccak 单向散列函数推导出地址

具体分为以下三个步骤:

  1. 创建随机私钥 (64 位 16 进制字符 / 256 比特 / 32 字节)
  2. 从私钥推导出公钥 (128 位 16 进制字符 / 512 比特 / 64 字节)
  3. 从公钥推导出地址 (40 位 16 进制字符 / 160 比特 / 20 字节)

以太坊黄皮书上关于钱包(私钥、公钥、地址)的描述如下:

椭圆曲线

公钥密码学

加密通常分为对称加密非对称加密。在对称密码中,由于加密和解密的密钥相同,所以必须向接收者配送密钥用于解密。但发送密钥过程中,窃听者可以窃取密钥解密,不发送密钥吧,接收者无法解密,密钥必须发送,但又不能发送,这问题称为密钥配送问题。在非对称加密中,将密钥分为加密密钥和解密密钥,也就是我们常说的公钥和私钥。

一个公钥密码学系统必须满足如下要求:

  1. 根据一个给定的公钥从而推导出它对应的私钥在计算上是不可行的。
  2. 可以证明一个人知道与该公钥相对应的私钥,而不透露关于私钥的任何有用信息

椭圆曲线原理

椭圆曲线由满足下列形式方程的所有点组成: y 2 = x 3 a x b y^2=x^3 ax b y2=x3 ax b 其中 4 a 3 27 b 2 ≠ 0 4a^3 27b^2neq0 4a3 27b2​=0(避免奇点)。

点加法

椭圆曲线上的点加法按照如下方式定义。

如果想要得到 点 P P P 和 点 Q Q Q 相加的结果:

首先,找到经过这两点的直线:

这条直线与椭圆曲线相交于第三个点:

最后,将这个交点关于 x x x轴 做对称:

其中,点 R R R 为 点 P P P 和 点 Q Q Q 相加的结果,即 P Q = R P Q=R P Q=R 。

如果选取一个基准点 P P P ,使得它不断地加上自身,我们可以得到 1 ⋅ P , 2 ⋅ P , … , k ⋅ P 1cdot P,2cdot P,ldots,k cdot P 1⋅P,2⋅P,…,k⋅P 。点 P P P与自身相加的结果可以定义为过点 P P P 的切线与椭圆曲线相交得到交点,交点再关于 x x x轴 对称。

对于以太坊钱包中用到的椭圆曲线 s e c p 256 k 1 secp256k1 secp256k1 选取的基准点的 x x x轴 和 y y y轴 坐标分别为:

x = 55066263022277343669578718895168534326250603453777594175500187360389116729240 x=55066263022277343669578718895168534326250603453777594175500187360389116729240 x=55066263022277343669578718895168534326250603453777594175500187360389116729240

y = 32670510020758816978083085130507043184471273380659243275938904335757337482424 y=32670510020758816978083085130507043184471273380659243275938904335757337482424 y=32670510020758816978083085130507043184471273380659243275938904335757337482424

快速点加法

如果 x x x 是一个随机的 256 256 256 位的整数,那么需要多少步才能算出 x ⋅ P xcdot P x⋅P 呢?我们可以知道, x ⋅ P xcdot P x⋅P 的结果可以在 510 510 510 次点加法之内被计算出来。具体分析流程如下。

首先,计算出以下序列:

2 0 ⋅ P , 2 1 ⋅ P , 2 2 ⋅ P , 2 3 ⋅ P , 2 4 ⋅ P , … , 2 255 ⋅ P 2^0cdot P, 2^1cdot P, 2^2cdot P, 2^3cdot P, 2^4cdot P, ldots, 2^{255}cdot P 20⋅P,21⋅P,22⋅P,23⋅P,24⋅P,…,2255⋅P

全部序列的计算需要 255 255 255次点加法。注意到, 2 n ⋅ P 2 n ⋅ P = 2 n 1 ⋅ P 2^ncdot P 2^ncdot P=2^{n 1}cdot P 2n⋅P 2n⋅P=2n 1⋅P 。于是任意的 x x x 都可以由上述的序列所表示。如 1 000 … 000 ⏟ 254 个 0 1 1underbrace{000ldots 000}_{254个0}1 1254个0 000…000​​1 可以由 2 255 ⋅ P 2 0 ⋅ P 2^{255}cdot P 2^0cdot P 2255⋅P 20⋅P 表示。因此,通过计算出相应的序列,再由这些序列表示对应的数值最多需要 510 510 510 次计算。

公钥和私钥

根据椭圆曲线点加法的定义,我们可以知道如果 X = x ⋅ P X=xcdot P X=x⋅P ,在给定 X X X 的情况下我们无法计算出对应的 x x x(每进行一次点加法,点的位置都变化很大,因此我们无法预测出在给定初始点的情况下,需要经过多少次加法才能得到最终的点),但是在给定 x x x 的情况下,我们却可以快速地计算出 X X X 。因此,椭圆曲线密码学就利用了这一原理,在给定基准点 P P P 的情况下,随机生成私钥 x x x ,再利用椭圆曲线快速计算出公钥 X X X 。

由于上述模型计算出的点无法表示 512 512 512 位的标准公钥(因为上面计算的结果很有可能是浮点数, 512 512 512 位通常不够表示相应的结果),因此我们需要定义我们的椭圆曲线在一个有限域上,也就是说曲线上所有点的坐标取值只能为整数。因此,我们将椭圆曲线的定义转化为:

y 2 ≡ x 3 a x b ( m o d p ) y^2equiv x^3 ax bqquad (modquad p) y2≡x3 ax b(modp)

其中 p p p 为 小于 2 256 2^{256} 2256 的最大质数。因此,修改后的椭圆曲线可能如下所示:

注意到,其中关于x轴对称的部分没有画出(这个部分不是很明白)。

应用

如何应用椭圆曲线?需要考虑两个问题:

  1. 如何证明你知道私钥 x x x 。
  2. 在证明过程中会不暴露关于私钥 x x x 的相关信息。
问题一

对于第一个问题,我们可以根据 X = x ⋅ P X=xcdot P X=x⋅P 计算出 X X X 如果我们知道 x x x 的话。根据椭圆曲线点加法的定义,我们可以知道:

x ⋅ P r ⋅ P = ( x r ) ⋅ P xcdot P rcdot P=(x r)cdot P x⋅P r⋅P=(x r)⋅P

我们将上式进行修改可以得到:

h a s h ( m , r ⋅ P ) ⋅ x ⋅ P r ⋅ P = ( h a s h ( m , r ⋅ P ) × x r ) ⋅ P hash(m,rcdot P)cdot xcdot P rcdot P=(hash(m,rcdot P)times x r)cdot P hash(m,r⋅P)⋅x⋅P r⋅P=(hash(m,r⋅P)×x r)⋅P

由于 X = x ⋅ P X=xcdot P X=x⋅P :

h a s h ( m , r ⋅ P ) ⋅ X r ⋅ P = ( h a s h ( m , r ⋅ P ) × x r ) ⋅ P hash(m,rcdot P)cdot X rcdot P=(hash(m,rcdot P)times x r)cdot P hash(m,r⋅P)⋅X r⋅P=(hash(m,r⋅P)×x r)⋅P

令 R = r ⋅ P , s = h a s h ( m , R ) × x r R=rcdot P, s=hash(m,R)times x r R=r⋅P,s=hash(m,R)×x r ,可以得到:

h a s h ( m , R ) ⋅ X R = s ⋅ P (1) hash(m,R)cdot X R=scdot P tag{1} hash(m,R)⋅X R=s⋅P(1)

如果我们知道 x x x ,我们可以选定一个 m , r m, r m,r ,从而根据 R = r ⋅ P , s = h a s h ( m , R ) × x r R=rcdot P, s=hash(m,R)times x r R=r⋅P,s=hash(m,R)×x r 计算出 R R R 和 s s s 使得(1)成立。于是,我们可以得出结论,如果能够给出 m , R , s m, R, s m,R,s 使得(1)成立,那么便能够证明其知道私钥 x x x

如果我们不知道 x x x ,想要使得(1)成立。固定任意两个数,去寻找第三个数,这个过程根据圆锥曲线点加法的计算方式,都是计算上不可行的

因此,我们可以得出结论:

  1. 如果知道 x x x ,那么应该能够提供 m , R , s m, R, s m,R,s 的工作值。
  2. 如果不知道 x x x ,那么应该不能提供 m , R , s m, R, s m,R,s 的工作值。
问题二

首先, m , R m, R m,R 的值与 x x x 无关,因此它不能揭示任何与私钥 x x x 相关的信息。

我们知道 s = h a s h ( m , R ) × x r s=hash(m,R)times x r s=hash(m,R)×x r ,因此我们可以得到:

x = s − r h a s h ( m , R ) x=frac{s-r}{hash(m,R)} x=hash(m,R)s−r​

因此,要想知道私钥 x x x 的值,我们需要首先获取 r r r 的值,但是根据 R = r ⋅ P R=rcdot P R=r⋅P ,由椭圆曲线点加法的计算方式,我们无法从 R R R 的取值来推导 r r r 的取值(计算上不可行)。

所以,在证明过程中不会暴露关于私钥 x x x 的信息

数字签名

通过验证等式 h a s h ( m , R ) ⋅ X R = s ⋅ P hash(m,R)cdot X R=scdot P hash(m,R)⋅X R=s⋅P 是否成立, m , R , s m, R, s m,R,s 可以被用来证明某人知道公钥 X X X 对应的私钥 x x x ,其中 X = x ⋅ P X=xcdot P X=x⋅P 。

如果 m m m 是一个给定的具体信息(用户即将发送),那么用户只需要提供 R R R 和 s s s 作为其数字签名,最终接收方验证等式 (1)是否成立来验证这个信息是否是由相应的用户所发生的(即用户对这条消息进行了签名认证)。如果验证通过,说明是由本人发送的这条消息,并不是其他人伪造的,在区块链上,这条消息就是一个交易请求信息,如果矿工验证通过了这个消息,那么他将会执行这条消息中的内容。

也就是说,对于一个(发送者发出的)具体消息 m m m ,通过提供数字签名 R R R 和 s s s 一个人可以证明他知道(发送者)公钥 X X X 对应的私钥 x x x (消息是由本人发出的)。

以太坊私钥存储(Keystore)文件

通常一些钱包会对用户的私钥进行加密等操作,使得私钥不会被直接暴露在外界,这提高了私钥的安全性。

以太坊客户端提供了私钥管理的功能,通过下述命令:

代码语言:javascript复制
$ geth account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase:
Repeat passphrase: 
Address: { 
   008aeeda4d805471df9b2a5b0f38a0c3bcba786b}

$ geth account list
Account #0: { 
   8a1c4d573cc29a96547816522cfe0b266e88abac} keystore:~/.ethereum/keystore/UTC--<created_date_time>-- 008aeeda4d805471df9b2a5b0f38a0c3bcba786b

上述命令将会创建一个新的账户,账户信息以 keystore 文件的形式存放在 ~/.ethereum/keystore 目录( Linux 操作系统)下。如果你丢失了这个文件,你就丢失了私钥,意味着你失去了签署交易的能力,意味着你的资金被永久的锁定在了你的账户里。

当然,你可以直接把你的以太坊私钥存储在一个加密文件里,但是这样你的私钥容易受到攻击,攻击者简单的读取你的文件、用你的私钥签署交易,把钱转到他们的账户中。你的币会在你意识到发生什么了之前的短时间内丢失。

这就是以太坊 keystore 文件被创建的原因:它允许你以加密的方式存储密钥。这是安全性(一个攻击者需要 keystore 文件和你的密码才能盗取你的资金)和可用性(你只需要keystore文件和密码就能用你的钱了)两者之间完美的权衡。

为了让你发送一些以太币,大多数的以太坊客户端会让你输入密码(与创建账户时密码相同)以解密你的以太坊私钥。一旦解密,客户端程序就得到私钥签署交易,允许你移动资金。

Keystore 文件格式

大致内容如下:

代码语言:javascript复制
{
    "crypto" : {
        "cipher" : "aes-128-ctr",
        "cipherparams" : {
            "iv" : "83dbcc02d8ccb40e466191a123791e0e"
        },
        "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
        "kdf" : "scrypt",
        "kdfparams" : {
            "dklen" : 32,
            "n" : 262144,
            "r" : 1,
            "p" : 8,
            "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
        },
        "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
    },
    "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
    "version" : 3
}

其中包括

  • cipher:对称 AES 算法的名称;
  • cipherparams:上述 cipher 算法需要的参数;
  • ciphertext:你的以太坊私钥使用上述 cipher 算法进行加密后的密文;
  • kdf:密钥生成函数,用于让你用密码加密 keystore 文件;
  • kdfparams:上述 kdf 算法需要的参数;
  • Mac:用于验证密码的代码(校验项);

工作流程

1. 加密你的私钥

为了确保你的私钥没有在文件中明文存储(即任何人只要能得到这个文件就能读),使用强对称算法(cipher)对其加密至关重要。

这些对称算法使用密钥来加密数据。加密后的数据可以使用相同的方法和同样的密钥来解密,因此算法命名为对称算法。在本文中,我们称这个对称密钥为解密密钥,因为它将用于对我们的以太坊私钥进行解密。

以下是 ciphercipherparamsciphertext 对应的概念:

  • cipher 是用于加密以太坊私钥的对称加密算法。此处 cipher 用的是 aes-128-ctr 加密模式。
  • cipherparamsaes-128-ctr 加密算法需要的参数。在这里,用到的唯一的参数 iv ,是 aes-128-ctr 加密算法需要的初始化向量。
  • ciphertext 密文是 aes-128-ctr 函数的解密的输入。

所以,在这里,你已经有了进行解密以太坊私钥计算所需要的一切。等等。你需要首先取回你的解密密钥。

2. 用你的密码来保护它

要确保解锁你的账户很容易,你不需要记住你的每一个又长又非用户友好型的用于解密 ciphertext 密文解密密钥。相反,以太坊开发者选择了基于密码的保护,也就是说你只需要输入密码就能拿回解密密钥

为了能做到这一点,以太坊用了一个密钥生成函数,输入密码和一系列参数就能计算解密密钥

这就是 kdfkdfparams 的用途:

  • kdf 是一个密钥生成函数,根据你的密码计算(或者取回)解密密钥。在这里,kdf 用的是 scrypt 算法。
  • kdfparamsscrypt 函数需要的参数。在这里,简单来说,dklen、n、r、psaltkdf 函数的参数。

在这里,用 kdfparams 参数对 scrypt 函数进行调整,反馈到我们的密码中,你就会得到解密密钥也就是密钥生成函数的输出。

3. 确保你的密码是对的

我们描述了用密码和 keystore 文件生成以太坊私钥所需要的所有东西。然而,如果解锁账户的密码错误会发生什么?

根据迄今为止我们所看到的,所有操作(密码派生和解密)都会成功,但是最终计算的以太坊私钥不是正确的,这首先违背了密钥文件的使用初衷!

我们要保证输入解锁账户的密码是正确的,和最初创建 keystore 文件时一样(回想一下 geth 下创建新账户时两次输入的密码)。

这就是 keystore 文件中 mac 值起作用的地方。在密钥生成函数执行之后,它的输出(解密密钥)和 ciphertext 密文就被处理,并且和 mac(就像一种认可的印章)作比较。如果结果和 mac 相同,那么密码就是正确的,并且解密就可以开始了。处理过程如下图所示:

总体流程

系统总体工作流程如下:

  1. 输入密码
  2. 这个密码kdfparams 作为 kdf 密钥生成函数的输入,来计算解密密钥
  3. 解密密钥chipertext 进行连接并进行处理,和 mac 进行比较来确保密码的正确性。
  4. 通过 chiper 对称函数(参数为 chiperparams )用解密密钥chipertext 密文解密,解密结果为账户的私钥。

钱包技术概述

第一类是非确定性钱包,其中保存的每一个私钥都是通过不同的随机数相互独立地生成的。私钥之间没有任何关联。这类钱包被称为 JBOK (Just a Bunch Of Keys)钱包。

第二类钱包是确定性钱包,其中所有的密钥都是从一个主密钥衍生而来的,这个主密钥就是种子密钥。这类钱包中所有的密钥之间都存在关联关系,如果获得了“种子密钥”,则可以重新生成所有密钥。确定性钱包有多种密钥派生方法。最常用的派生方法是使用一个类似树形的结构,我们称之为层级式确定性(hierarchical deterministic)钱包,或简称为 HD 钱包。

注:用户如果长期使用同一个账户地址进行交易,那么用户的账户就可能存在被跟踪的风险,导致用户的隐私泄露。因此,为了保证隐私性,通常需要用户不断更换发起交易的地址。在比特币网络中,由于账户模型是 UTXO 模型,每笔交易可以设置一个找零账户,交易中剩余的金额会被转入这个账户,这就使得同一个用户可以使用不同的地址发起交易(交易结束后将剩余的钱转入一个新的地址,这个在以太坊上不适用,因为以太坊需要收取交易的手续费)。这就使得一个用户可能同时拥有多个比特币的地址,因此要管理多个这样的地址,钱包中也就需要管理多个私钥。由于非确定性钱包中,各个私钥之间不相关,因此管理起来相对麻烦(需要同时管理多个私钥),而确定性钱包各个私钥之间存在关联,可以通过主密钥来派生出所有的子密钥,因此钱包中只需要管理主密钥,就可以衍生出所有相关的密钥。

非确定性钱包

上述以太坊通过存储 keystore 文件来管理密钥,就是一种确定性钱包。

确定性钱包

主要以层级式确定性钱包为例。它的目的是让人们更容易地从单一的“种子”中衍生出多个密钥。目前,确定性钱包最高级的形式便是由比特币 BIP-32 标准定义的 HD 钱包。HD钱包可以保存用树状结构推导的多个密钥,比如一把私钥可以推导出一系列子密钥,每一个子密钥都可以推演出一系列孙子密钥,如此类推至于无穷。如图所示:

其中 BIP-39 中提出了利用助记词生成种子密钥的标准,这使得人们不用去与复杂的密钥(二进制串)打交道,而是使用相对易于理解的助记词。

以下是一些钱包设计中的行业标准 BIP (Bitcoin Improvement Proposal):

  • 基于 BIP-32 的层级式确定性钱包的
  • 基于 BIP-39 的助记词标准
  • 基于 BIP-43 的多用途层级式确定性钱包
  • 基于 BIP-44 的多币种和多账户钱包

助记词标准 (BIP-39)

助记词中的单词代表用来生成钱包的种子密钥中的内容。这一串助记词足够用来重新创建种子密钥,进而恢复整个钱包中所有从这个种子派生而来的密钥。本节说明了如何生成助记词以及如何通过助记词创建种子密钥。

生成助记词

助记词由钱包根据BIP—39所定义的标准流程自动生成,钱包从随机源获取一个随机数,然后添加校验码,再把这个数字映射为一串英文单词:

  1. 创建一个 128 128 128 比特或 256 256 256 比特的密码学强度的随机数,我们姑且称之为 S S S 。
  2. 取出 S S S 的 S H A − 256 SHA-256 SHA−256 哈希值的前( S 的 长 度 / 32 S的长度/32 S的长度/32)比特,作为随机数 S S S 的校验值
  3. 将上一步得到的校验值加到随机数 S S S 的末尾。
  4. 以 11 11 11 比特为单位,将随机数 S S S 与校验值的结合数分成多个组。
  5. 将每一个 11 11 11 比特的值都根据预先定义的字典映射为单词(这个字典包含 2048 2048 2048 个简单的英文单词,正好覆盖所有 11 11 11 比特的可能范围)。
  6. 保持初始的次序,得出的单词字符串即我们所需的助记词。

从助记词到种子密钥

助记词代表128比特或256比特的随机数。使用密钥扩展算法,例如 PBKDF2,可以将这个随机数衍生成 512 512 512 比特长的种子,进而用来构建确定性钱包和派生其他密钥。

密钥扩展算法需要两个参数:助记词和盐(salt) 。加盐的目的是防止通过循环表格的方式来实现暴力激活成功教程。在 BIP-39 标准中,加盐还有另外一个目的:引入额外的密码来保护种子密钥。

  1. PBKDFZ 密钥扩展算法的第一个参数是步骤 6 中产生的助记词。
  2. PBKDFZ 密钥扩展算法的第二个参数是“盐”,盐的内容由川户提出,可以是一个可选的密码,并跟 ”mnemonic” 组合在一起。
  3. PBKDFZ 针对助记词和盐进行 2048 2048 2048 轮哈希运算,使用的是 H M A C − S H A 512 HMAC-SHA512 HMAC−SHA512 算法,产生一个 512 512 512 比特的数作为最终输出。这个数字就是种户密钥。

BIP-39 标准允许用户在生成种子密钥的过程中使用可选密码。如果用户没有设定密码,那么默认使用字符串 ”mnemonic” 进行助记词的密钥扩展运算,生成一个特定的 512 512 512 比特的种子密钥。如果用户提供了密码,那么对于同样的助记词,密钥扩展运算会生成完全不同的种子密钥。实际上,给定一组助记词,每一个密码都会导致不同的种子密钥。特别是,这里没有正确或者错误的密码,所有密码都可以生成用来衍生无数钱包地址的种子密钥。可能的钱包的范围非常巨大,如果密码的复杂度足够强,那么暴力激活成功教程或猜测都没有可能实现。举个栗子

0 人点赞