libsecp256k1比特币密码算法开源库(十)

2021SC@SDUSC

secp256k1定义的结构有很多,主要会说明以下内容:
Message:哈希摘要;
PublicKey:公钥;
SecretKey:256比特私钥;
RecoveryId:用于从签名中恢复公钥的标记;
SharedSecret:ECDH共享密钥;
Signature:数字签名

这些结构的定义和实施都比较类似,本篇主要讲述Message。此外在这些结构中都使用了libsecp256k1中对大数长度的一些定义,在本篇中加以说明,方便后续的分析。

Message

哈希摘要Message总述

hash摘要在数字签名中非常常见,在区块链比特币场景中多处要用到数字签名,比如转账方要对转账记录进行数字签名,以确保转账由他本人发起;此外在区块链中还有些地方单独使用了hash函数,比如挖矿、对交易hash得到merkle树的树根、以及转账时转账方要给出接收方的公钥hash以便接收方在给别人转账时可以通过脚本引擎的验证……

在之前的博客中多次出现变量message,之前解释过使用的message是Scalar类型的256位标量值,这里专门为hash摘要创建一个类型Message:

pub struct Message(pub Scalar);

待加密信息要经过hash处理成为摘要才能参与签名生成、签名验证、公钥恢复等运算,那么这个hash摘要结过就是一个256比特的01串,Message类型实例化的对象就是这样的256比特01串,将这个对象转化为数组序列的过程称之为序列化(在代码中使用serialize表示);将数组序列恢复成为Message类型的一个对象的过程称为反序列化(在代码中用parse表示)。

首先在Message中定义了三个函数,分别是反序列化parse、切片parse_slice和序列化serialize:

impl Message {
//反序列化:将数组序列恢复成为Message类型的一个对象
    pub fn parse(p: &[u8; util::MESSAGE_SIZE]) -> Message {
        let mut m = Scalar::default();
        let _ = m.set_b32(p);

        Message(m)
    }
//切片
    pub fn parse_slice(p: &[u8]) -> Result<Message, Error> {
        if p.len() != util::MESSAGE_SIZE {
            return Err(Error::InvalidInputLength);
        }//如果p的长度不为常量MESSAGE_SIZE定义值32则报错

        let mut a = [0; util::MESSAGE_SIZE];
        a.copy_from_slice(p);
        Ok(Self::parse(&a))
    }
//序列化:将这个Message对象转化为数组序列
    pub fn serialize(&self) -> [u8; util::MESSAGE_SIZE] {
        self.0.b32()
    }
}

反序列化parse

首先是parse反序列化过程:
libsecp256k1库为message长度定义一个常量MESSAGE_SIZE,由于usize = 32,这表示message共32单位长度:

pub const MESSAGE_SIZE: usize = 32;

message共32单位长度,那么一个单位是多大呢?
下面代码中有p: &[u8; util::MESSAGE_SIZE],这表示的意思就是变量p用数组元素存储(p是一个序列化的变量),u8表示数组元素是无符号char类型,存储8位比特;util::MESSAGE_SIZE变量p共包含多少个数组元素,前面说过,这里的MESSAGE_SIZE是个常量,为32。那么这其实就表示变量p包含32个8比特的数组元素,即共包含256比特,刚好是一个hash摘要的长度。
那么接下来只要创建一个Scalar类型的变量m来接收p即可,就把p转化为了一个Message类型的变量m,实现了反序列化的过程。

 pub fn parse(p: &[u8; util::MESSAGE_SIZE]) -> Message {
        let mut m = Scalar::default();
        let _ = m.set_b32(p);

        Message(m)
    }

实际上这里是调用了一个set_b32的函数实现了反序列化,就是基本的移位、与或运算等,没有太多解释的必要,相关运算法则我在libsecp256k1比特币密码算法开源库(八)中曾经介绍过,这里不再细说。具体set_b32实现过程如下代码所示:

#[must_use]
    pub fn set_b32(&mut self, b32: &[u8; 32]) -> Choice {
        self.0[0] = (b32[31] as u32)
            | ((b32[30] as u32) << 8)
            | ((b32[29] as u32) << 16)
            | ((b32[28] as u32) << 24);
        self.0[1] = (b32[27] as u32)
            | ((b32[26] as u32) << 8)
            | ((b32[25] as u32) << 16)
            | ((b32[24] as u32) << 24);
        self.0[2] = (b32[23] as u32)
            | ((b32[22] as u32) << 8)
            | ((b32[21] as u32) << 16)
            | ((b32[20] as u32) << 24);
        self.0[3] = (b32[19] as u32)
            | ((b32[18] as u32) << 8)
            | ((b32[17] as u32) << 16)
            | ((b32[16] as u32) << 24);
        self.0[4] = (b32[15] as u32)
            | ((b32[14] as u32) << 8)
            | ((b32[13] as u32) << 16)
            | ((b32[12] as u32) << 24);
        self.0[5] = (b32[11] as u32)
            | ((b32[10] as u32) << 8)
            | ((b32[9] as u32) << 16)
            | ((b32[8] as u32) << 24);
        self.0[6] = (b32[7] as u32)
            | ((b32[6] as u32) << 8)
            | ((b32[5] as u32) << 16)
            | ((b32[4] as u32) << 24);
        self.0[7] = (b32[3] as u32)
            | ((b32[2] as u32) << 8)
            | ((b32[1] as u32) << 16)
            | ((b32[0] as u32) << 24);

        let overflow = self.check_overflow();
        self.reduce(overflow);

        overflow
    }

这个代码的就是实现将一个[u8; 32](一个数组元素占8位,共32个数组元素的变量)字段元素转化为一个32字节(一个数组元素为u32类型,即32比特,共8个数组元素)的大端序(就是Scalar类型变量),并且转化结束后生成的大端值将被规范化。

值得注意,这里使用了set_b32实现Scalar类型变量反序列化,在以后要说的公钥部分也会使用set_b32函数,但不是同一个,因为Scalar类型变量32字节,而公钥作为椭圆曲线群上的一个点必然是一个Field元素,其x和y坐标反序列化结果为40字节,用10个数组元素存储u32类型的元素,共320比特即40字节。

序列化serialize

序列化就是把反序列化反过来,将一个Scalar类型(也可以认为是Message类型,因为专门创建一个Message结构体)转化为一个[u8; util::MESSAGE_SIZE]的数组变量,这个转化过程通过函数b32()来实现

pub fn serialize(&self) -> [u8; util::MESSAGE_SIZE] {
        self.0.b32()
    }

b32实现代码如下,显然它又调用了一个fill_b32函数(套娃警告),在b32中创建了一个数组变量bin,与Message类型变量序列化后的变量结构一致:

pub fn b32(&self) -> [u8; 32] {
        let mut bin = [0u8; 32];
        self.fill_b32(&mut bin);
        bin
    }

在介绍fill_b32之前合理想象,每个数组元素是u8类型占8位,数组共32个元素,这个代码也短不了,实际上就是用32个u8类型的数组元素接收Message类型的对应位。

 pub fn fill_b32(&self, bin: &mut [u8; 32]) {
        bin[0] = (self.0[7] >> 24) as u8;
        bin[1] = (self.0[7] >> 16) as u8;
        bin[2] = (self.0[7] >> 8) as u8;
        bin[3] = (self.0[7]) as u8;
        bin[4] = (self.0[6] >> 24) as u8;
        bin[5] = (self.0[6] >> 16) as u8;
        bin[6] = (self.0[6] >> 8) as u8;
        bin[7] = (self.0[6]) as u8;
        bin[8] = (self.0[5] >> 24) as u8;
        bin[9] = (self.0[5] >> 16) as u8;
        bin[10] = (self.0[5] >> 8) as u8;
        bin[11] = (self.0[5]) as u8;
        bin[12] = (self.0[4] >> 24) as u8;
        bin[13] = (self.0[4] >> 16) as u8;
        bin[14] = (self.0[4] >> 8) as u8;
        bin[15] = (self.0[4]) as u8;
        bin[16] = (self.0[3] >> 24) as u8;
        bin[17] = (self.0[3] >> 16) as u8;
        bin[18] = (self.0[3] >> 8) as u8;
        bin[19] = (self.0[3]) as u8;
        bin[20] = (self.0[2] >> 24) as u8;
        bin[21] = (self.0[2] >> 16) as u8;
        bin[22] = (self.0[2] >> 8) as u8;
        bin[23] = (self.0[2]) as u8;
        bin[24] = (self.0[1] >> 24) as u8;
        bin[25] = (self.0[1] >> 16) as u8;
        bin[26] = (self.0[1] >> 8) as u8;
        bin[27] = (self.0[1]) as u8;
        bin[28] = (self.0[0] >> 24) as u8;
        bin[29] = (self.0[0] >> 16) as u8;
        bin[30] = (self.0[0] >> 8) as u8;
        bin[31] = (self.0[0]) as u8;
    }

切片parse_slice

虽然这个函数取了一个slice切片的名字,但我认为这单纯只是一个复制,并使用了一个可修复错误的错误处理Result<T, E>来接收结果(其中T对应正常访问值的数据类型, E对应错误返回值的数据类型)。这个函数的作用就是将序列化元素(下面代码中的变量p)复制存放在序列化元素数组(下面代码中的变量a)中,然后再调用反序列化函数parse将序列化数组a给反序列化。

 pub fn parse_slice(p: &[u8]) -> Result<Message, Error> {
        if p.len() != util::MESSAGE_SIZE {
            return Err(Error::InvalidInputLength);
        }//错误,如果p的长度不为32则报错

        let mut a = [0; util::MESSAGE_SIZE];
        a.copy_from_slice(p);
        Ok(Self::parse(&a))//没有错误,结果为反序列化
    }

具体的复制操作由函数copy_from_slice来实现,就是把序列化数组p中的元素给复制到一个新数组a中。

utils相关参数定义

在前文中有一个util::MESSAGE_SIZE,常量MESSAGE_SIZE定义了一个message的长度,除此之外libsecp256k1中还定义了很多常量包括私钥长度、签名长度等,为了后面继续介绍相应的结构,在这里贴个全的,后面有用到对应的还会单独拿出来加以说明。

pub mod util {
    pub const TAG_PUBKEY_EVEN: u8 = 0x02;//公钥y坐标为偶数时添加前缀0x02
    pub const TAG_PUBKEY_ODD: u8 = 0x03;//公钥y坐标为奇数时添加前缀0x03
    pub const TAG_PUBKEY_FULL: u8 = 0x04;//公钥未压缩时添加前缀0x04
    pub const TAG_PUBKEY_HYBRID_EVEN: u8 = 0x06;
    pub const TAG_PUBKEY_HYBRID_ODD: u8 = 0x07;

    pub const MESSAGE_SIZE: usize = 32;//哈希摘要共32字节
    pub const SECRET_KEY_SIZE: usize = 32;//私钥共32字节
    pub const RAW_PUBLIC_KEY_SIZE: usize = 64;//原始公钥共64字节
    pub const FULL_PUBLIC_KEY_SIZE: usize = 65;//未压缩公钥65字节
    pub const COMPRESSED_PUBLIC_KEY_SIZE: usize = 33;//压缩公钥33字节
    pub const SIGNATURE_SIZE: usize = 64;//数字签名64字节
    pub const DER_MAX_SIGNATURE_SIZE: usize = 72;//数字签名DER序列化72字节

    pub use crate::{
        ecmult::{
            odd_multiples_table, ECMULT_TABLE_SIZE_A, ECMULT_TABLE_SIZE_G, WINDOW_A, WINDOW_G,
        },
        group::{globalz_set_table_gej, set_table_gej_var, AFFINE_INFINITY, JACOBIAN_INFINITY},
    };

    pub use crate::der::{Decoder, SignatureArray};
}

版权声明:本文为qq_50680426原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。