作者:XHH_111
时间:2021年10月24日
来源:https://blog.csdn.net/m0_55633961/article/details/120861780
2021SC@SDUSC
本篇博客探讨一下序列化的内容
目录
一、公钥序列化
SEC,高效加密标准,是序列化ECDSA公钥的标准,他的开销最小。目前分为压缩格式和未压缩格式。
1、未压缩的SEC格式
生成一个给定点p=(x,y)的未压缩SEC格式的步骤如下:
1.以0x04作为前缀。
2.以大端序整数的形式放入32字节的x轴坐标。
3.以大端序整数的形式放入32字节的y轴坐标。
问题:大端序和小端序是为了解决数字存储到硬盘的问题。一个小于256的数字很容易编码,单个字节(2^8)足以容纳。但是如果数字大于256我们如何将其序列化为字节?
答:计算机中的数据是以字节来存储,我们将使用256进制来计算。这意味着十进制数字500的大端序表示为01f4,即500=1*256+244(f4是十六进制的),小端序表示为f401(f4+01*256)。
注意:比特币序列化中有的采用大端序,有的小端序,注意区分。
2、压缩的SEC格式
对于任何x坐标,基于椭圆曲线中的y^2项,最多有两个y坐标。在有限域上也存在同样的对称性。
这是因为对于任意(x,y),如果满足y^2=x^3+ax+b,那么(x,-y)也会在曲线上、进而在有限域上,有-y%p=(p-y)%p。也就是说,如果(x,y)在有限域的椭圆曲线上,(x,p-y)也在曲线上。因为对于一个x只有两个解,y和p-y。
因为p是一个大于2的质数,所以p也是奇数。如果y是偶数,则p-y是奇数(奇数减偶数结果为奇数)。如果y是奇数,则p-y是偶数(奇数减奇数结果为偶数)。也就是说,在y和p-y中必然有一个是奇数,另一个是偶数。我们可以利用这一点来压缩未压缩的SEC格式:提供x坐标和y坐标的奇偶性。我们称这个压缩方法为压缩的SEC格式 。y坐标被压缩成单个字节(即偶数或奇数)。
下面是对于点P= (x, y)采用压缩的SEC格式序列化的步骤:
1.以y的奇偶性作为前缀,如果y是偶数,则为0x02, 否则为0x03。
2.以大端序整数的形式放入32字节的x轴坐标。
压缩的SEC格式的巨大优势是只占用了33个字节而不是65个,节省很大空间。
问题:如何根据x算出y?
答:在有限域中计算平方根。即数学上,当给定v,计算满足w^2=v (%p)的w:
如:有限域质数p%4=3,那么,
(p+1)%4=0,
这说明(p+1)/4是一个整数。
根据定义w^2=v (%p) ,
我们要找到一个计算w的公式,根据费马小定理:
w^(p-1)%p=1,
可以得出w^2=w ^2*1=w^2*w^(p-1)=w^(p+1),
因为p是奇数(也是质数),所以 (p+1)除以2得到整数,得出:
w=w ^((p+1)/2)
利用(p+1)/4为整数的性质可得:
w=w ^((p+1)/2)=w ^(2(p+1)/4)=(w ^2) ^((p+1)/4)=v ^((p+1)/4)
至此我们获得了平方根公式:
w ^2=v且p%4=3时,w=v ^((p+1)/4),实际上也就是libsecp256k1的椭圆曲线公式。
w=v ^((p+1)/4)是w的可能值之一,另一个是p-w,因为求平方根结果一正一负,他们互为相反数。
但我们想序列化一个SEC公钥时,我们可以编写parse来计算y:
如果公钥为NULL或无效,ec_pubkey_serialize将调用非法的_回调并返回0。在这种情况下,我们将把密钥序列化为小于任何有效公钥的全零。即使涉及空的或无效的公钥,这也会导致一致的比较,并防止边缘情况,例如使用此函数且不会因此终止的排序算法。
请注意,在这种情况下,ec_pubkey_serialize应该已经将输出设置为零,但API不保证输出为零,我们无法对其进行测试,编写验证检查比显式memsetting(再次)更复杂。
二、数字签名序列化(DER签名)
另一个我们需要序列化的类是signature。与SEC格式样我们要对两个不同的数字r和s编码。因为不能只根据r计算出s,所以Signature不能被压缩。
签名序列化的标准是DER (Distinguished Encoding Rules,可分别编码规则)格式。DER格式被中本聪采用作为序列化签名的方法。最可能的原因是这个标准在2008年确立,并且得到OpenSSL库(比特币当时使用的库)的支持。与其创造一个新的标准,不如简单地采纳适应已有标准。
DER签名格式如下定义:
1.以0x30字节作为前缀。
2.编码剩余签名的长度(通常为0x44或者0x45)。
3.追加标记字节 0x02。
4.以大端序编码r,如果r的第一个节大于等于0x80,则在r前置0x00,计算r序列化的长度并置于r的编码结果前,追加以上内容。
5.追加标记字节0x02。
6. 以大端序编码s,如果s的第一个字节大于等于0x80, 则在s前置0x00, 计算s序列化的长度并置于S的编码结果前,追加以上内容。
第4条和第6条规定了待序列化的数据第一个字节大于0x80的情况,因为DER是一个通用的编码规则,所以允许负数编码,第一位(二进制转换后的第一-位) 为1意味着数字为负数。ECDSA的签名数据中的数字都为正数,所以如果签名数字二进制转化后第一位为1(等价于第一个字节大于等于0x80),我们需要前置0x00。
我们知道r是一个256比特的整数,大端序最多需要32字节来表示。因为第一个字节可能大于等于0x80,所以步骤4最多有33个字节。但如果r是一个相对小的数字,可能小于32个字节就能表示它。同样的情况对步骤6也适用。
实现代码如图所示:
三、私钥序列化(WIF格式)
在我们的示例中,私钥是256位的数字。一般来说, 不需要经常序列化私钥。私钥也不需要广播(广播私钥是非常糟糕的想法)。但在某些情况下,你可能需要将私钥从个钱包传输到另一个钱包,例如,从纸质钱包到软件钱包。
为此,你可以使用钱包导入格式(Wallet Import Format, WIF)。 WIF是一个序列化私钥的方法,这意味着WIF是人类可读的格式,WIF使用Base58格式编码。
下面是WIF格式的生成步骤:
1.对于主链的私钥,以0x80为前缀;测试链使用0xef。
2.大端序编码私钥为32字节。
3.如果使用压缩的SEC公钥地址则增加后缀0x01。
4.结合步骤1的前缀步骤2的序列化私钥及步骤3的后缀。
5.对步骤4的结果进行hash256,取其前四个字节。
6.结合步骤4和步骤5的结果,并使用Baes58对其编码。
测试链:测试链时开发者使用的与比特币平行的网络,测试链上的币没有价值,新块需要的工作量证明的难度相对较低
四、地址格式
压缩后的SEC格式仍然有264比特,仍然很长。为了缩短地址并且提高安全性。我们可以使用ripemd160哈希运算。
与直接使用SEC格式相比,我们可以把长度从33字节显著地降低为20字节。以下是比特币地址的生成步骤:
1.对于比特币主链地址,采用0x00前缀,测试链采用0x6f。
2.对SEC格式(包括压缩的和未压缩的)做一次sha256运算,之后再做一次ripemd160哈希运算。这两次哈希运算被称为一次hash160运算。
3.将步骤1的前缀和步骤2的结果拼接。
4. 对步骤3的结果做一 次hash256, 并取其前四个字节。
5.拼接步骤3和步骤4的结果,使用Base58对其编码。
其中步骤4的计算过程被称为校验和,我们可以一并实现步骤4和步骤5。