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

Posted yyDrifter

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了libsecp256k1比特币密码算法开源库(十三)相关的知识,希望对你有一定的参考价值。

2021SC@SDUSC

DER签名(上)

除了序列化公钥外,还需要序列化的类是Signature。序列化签名与序列化公钥一样,公钥分别序列化x和y坐标,这里要对数字签名的两个不同的数字r和s编码(数字签名由256比特的r值和256比特的s值连接在一起构成)。
但是注意这里的r和s不具备像公钥x和y那样的对应关系(公钥的x坐标值代入到椭圆曲线方程中就可以得到y),因而只有公钥才有所谓的压缩公钥和未压缩公钥,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规定了待序列化的r和s第一个字节大于0x80的情况,因为DER是一个通用的编码规则(也就是说DER不止给比特币数字签名用),所以允许负数编码,这样的话如果r或s的第一位(二进制转换后的第一位) 为1就意味着数字为负数,而ECDSA的签名数据中的数字都为正数,所以如果签名数字二进制转化后第一位为1 (等价于第一个字节大于等于0x80),需要前置0x00。

r和s是一个256比特的整数。大端序最多需要32字节来表示,因为第一个字节可能大于等于0x80, 所以步骤4和6生成的r和s最多有33个字节(r和s本来最多32字节,如果在前面加一个0x00就是33字节了)。但如果r和s是一个相对小的数字,可能小于32个字节就能表示它。

即DER签名格式应该像下面这样:
0 x 30 [ t o t a l − l e n g t h ] 0 x 02 [ R − l e n g t h ] [ R ] 0 x 02 [ S − l e n g t h ] [ S ] [ s i g h a s h ] 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] 0x30[totallength]0x02[Rlength][R]0x02[Slength][S][sighash]
其中
total-length: 共一字节,表示其后的字节序列长度,不包括[sighash]部分。注意这里的total-length表示的不是整个DER数字签名的长度,而是该[total-length]字段之后的字节长度,但是为了表述方便,下文中就用该字段表示整个签名长度来叙述。
R-length: 共一字节,表示r部分长度。
R: 任意长度的大端序编码R值。它必须对正整数使用尽可能短的编码,这意味着在开始时没有空字节,除非r的第一个字节大于等于0x80,在r前置0x00。
S-length: 共一字节,表示s部分长度。
S: 任意长度的大端序编码S值,和R用同样的规则。
sighash: 一字节长度,该标志指定签名签署交易的哪个部分。

下面是相应代码:

bool static IsValidSignatureEncoding(const std::vector<unsigned char> &sig) 

    //签名最大和最小约束
    if (sig.size() < 9) return false;
    if (sig.size() > 73) return false;

    //DER签名的第一个字节应为0x30
    if (sig[0] != 0x30) return false;

    //确保第二个字段[total-length]为整个签名大小
    //-3代表减去0x30字段,[total-length]本身字段和不是DER签名一部分的[sighash]字段
    if (sig[1] != sig.size() - 3) return false;

    //提取R部分的长度
    unsigned int lenR = sig[3];

    // 确保签名包含S部分
    if (5 + lenR >= sig.size()) return false;

    //提取S部分的长度
    unsigned int lenS = sig[5 + lenR];

    //验证签名的实际长度是否与S部分长度、R部分长度和7的加和一致
    if ((size_t)(lenR + lenS + 7) != sig.size()) return false;
 
    //检查R元素是否为整数
    if (sig[2] != 0x02) return false;
    
    //R的字段长度不能为0
    if (lenR == 0) return false;

    //R值不能为负数
    if (sig[4] & 0x80) return false;

    // R开头不能为空字节,否则R会被当做负数。
    if (lenR > 1 && (sig[4] == 0x00) && !(sig[5] & 0x80)) return false;

    //检查S元素是否为整数
    if (sig[lenR + 4] != 0x02) return false;

    //S的字段长度不能为0
    if (lenS == 0) return false;

    //S值不能为负数
    if (sig[lenR + 6] & 0x80) return false;

    // S开头不能为空字节,否则S会被当做负数。
    if (lenS > 1 && (sig[lenR + 6] == 0x00) && !(sig[lenR + 7] & 0x80)) return false;

    return true;

数字签名Signature

在ECC椭圆曲线加密算法中的数字签名包括 r r r s s s两部分,这两部分都是标量,通过连接运算拼合在一起构成一个完整的数字签名。

pub struct Signature 
    pub r: Scalar,
    pub s: Scalar,

数字签名如果仅包含r和s两部分即为64字节,DER格式的数字签名最大为72字节。

pub const SIGNATURE_SIZE: usize = 64;//数字签名64字节
pub const DER_MAX_SIGNATURE_SIZE: usize = 72;//数字签名DER序列化72字节

这里的72是怎么来的呢,其实就是r的32字节+s的32字节+0x30 的1字节+[total-length]的1字节+ 0x02的1字节+[R-length]的1字节+0x02的1字节+[S-length]的1字节+r和s前面各有可能出现的0x00最多2字节。把这些加起来就是72字节。

在本篇中只介绍签名的反序列化和DER签名反序列化部分,剩余的函数实现参见libsecp256k1比特币密码算法开源库(十四)。下面是数字签名相关函数的实现:

impl Signature 
    //允许签名溢出的反序列化
    pub fn parse_overflowing(p: &[u8; util::SIGNATURE_SIZE]) -> Signature 
        let mut r = Scalar::default();
        let mut s = Scalar::default();

        let _ = r.set_b32(array_ref!(p, 0, 32));
        let _ = s.set_b32(array_ref!(p, 32, 32));

        Signature  r, s 
    

    //未溢出的签名的反序列化
    pub fn parse_standard(p: &[u8; util::SIGNATURE_SIZE]) -> Result<Signature, Error> 
        let mut r = Scalar::default();
        let mut s = Scalar::default();

        let overflowed_r = r.set_b32(array_ref!(p, 0, 32));
        let overflowed_s = s.set_b32(array_ref!(p, 32, 32));

        if bool::from(overflowed_r | overflowed_s) 
            return Err(Error::InvalidSignature);
        

        Ok(Signature  r, s )
    


    //复制未溢出的反序列化签名
    pub fn parse_standard_slice(p: &[u8]) -> Result<Signature, Error> 
        if p.len() != util::SIGNATURE_SIZE 
            return Err(Error::InvalidInputLength);
        

        let mut a = [0; util::SIGNATURE_SIZE];
        a.copy_from_slice(p);
        Ok(Self::parse_standard(&a)?)
    

    //将DER格式的签名反序列化
    pub fn parse_der(p: &[u8]) -> Result<Signature, Error> 
        let mut decoder = Decoder::new(p);

        decoder.read_constructed_sequence()?;
        let rlen = decoder.read_len()?;

        if rlen != decoder.remaining_len() 
            return Err(Error::InvalidSignature);
        

        let r = decoder.read_integer()?;
        let s = decoder.read_integer()?;

        if decoder.remaining_len() != 0 
            return Err(Error::InvalidSignature);
        

        Ok(Signature  r, s )
    
    //将s规范化为低s
    pub fn normalize_s(&mut self) 
        ...
    
    //将签名序列化为未溢出的格式,也就是上面的函数`parse_standard`的逆过程
    pub fn serialize(&self) -> [u8; util::SIGNATURE_SIZE] 
        ...
    
    //将签名序列化为DER编码格式,也就是函数`parse_der`的逆过程
    pub fn serialize_der(&self) -> SignatureArray 
        ...

数字签名反序列化

在数字签名中r和s由于进行了一个mod n运算(n为有限域的秩),因此r和s的值不能大于n:
r = x P   m o d   n s = k − 1 ( z + r d A )   m o d   n r=x_P\\, mod \\, n\\\\s=k^-1(z+rd_A) \\,mod \\,n r=xPmodns=k1(z+rdA)modn
在下面的代码中允许签名发生“溢出”,即r和s的值大于n;后面还会有parse_standard部分的代码,在那里如果r和s的值大于n会报错。

在下面的代码中通过调用set_b32函数对r和s分别进行反序列化,set_b32实现具体代码细节可以参加私钥部分,这里不再赘述,这里将r和s分别反序列化的过程与公钥将x和y分别反序列化思想类似,但是这里的r和s都是Scalar标量,而公钥的x和y是Field域元素。

    pub fn parse_overflowing(p: &[u8; util::SIGNATURE_SIZE]) -> Signature 
        let mut r = Scalar::default();
        let mut s = Scalar::default();

        //签名的r和s部分允许溢出
        let _ = r.set_b32(array_ref!(p, 0, 32));
        let _ = s.set_b32(array_ref!(p, 32, 32));

        Signature  r, s 
    

上面使用允许溢出的r和s理论上来说是安全的,但是并不标准,这意味着如果你也使用其他secp256k1库,可能会遇到兼容性问题,因此还是使用下面的’parse_standard’代替。在下面的parse_standard函数中,r和s可以大于n,但会返回错误,只有未溢出的r和s才会进行反序列化并返回反序列化的结果。

    pub fn parse_standard(p: &[u8; util::SIGNATURE_SIZE]) -> Result<Signature, Error> 
        let mut r = Scalar::default();
        let mut s = Scalar::default();

        //签名的r和s部分允许溢出,但是如果溢出下面会报错
        let overflowed_r = r.set_b32(array_ref!(p, 0, 32));
        let overflowed_s = s.set_b32(array_ref!(p, 32, 32));
        //如果r和s溢出,报错
        if bool::from(overflowed_r | overflowed_s) 
            return Err(Error::InvalidSignature);
        

        Ok(Signature  r, s )
    

下面的代码实现将反序列化的签名进行复制。这里的签名调用了parse_standard函数,也就是一个标准未溢出的反序列化签名。

    pub fn parse_standard_slice(p: &[u8]) -> Result<Signature, Error> 
        if p.len() != util::SIGNATURE_SIZE 
            return Err(Error::InvalidInputLength);
        

        let mut a = [0; util::SIGNATURE_SIZE];
        a.copy_from_slice(p);
        Ok(Self::parse_standard(&a)?)
    

DER数字签名反序列化

在上面的部分中实现了签名的反序列化,这里对DER格式的数字签名进行反序列化,两个都是实现对序列化的反序列化,这点看来没什么不同,但是上面的反序列化实际上只是对r和s的反序列化,在DER格式中序列化的数字签名不止包含r和s:
0 x 30 [ t o t a l − l e n g t h ] 0 x 02 [ R − l e n g t h ] [ R ] 0 x 02 [ S − l e n g t h ] [ S ] 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] 0x30[totallength]0x02[Rlength][R]0x02[Slength][S]
因此下面对于DER数字签名的相应处理就比较复杂,DER数字签名的反序列化函数代码实现如下所示:

    pub fn parse_der(p: &[u8]) -> Result<Signature, Error> 
        let mut decoder = Decoder::new(p);
        //检验首字节是否为0x30
        decoder.read_constructed_sequence()?;
        //读入[total-length]字段
        let rlen = decoder.read_len()?;
        //如果[total-length]字段描述长度与实际长度不符,返回错误
        if rlen != decoder.remaining_len() 
            return Err(Error::InvalidSignature);
        
       //分析 0x02 [R-length] [R]字段
        let r = decoder.read_integer()?;
       //分析 0x02 [S-length] [S]字段
        let s = decoder.read_integer()?;
       //如果剩余字段长度不为0,返回错误
        if decoder.remaining_len() != 0 
            return Err(Error::InvalidSignature);
        

        Ok(Signature  r, s )
    

这个代码比较复杂,过程中调用函数也比较多,我将parse_der代码实现结果和对应函数调用情况做了一个图,下面的具体代码分析过程可以结合这个图来理解。

首先是第一个函数read_constructed_sequence,这个函数首先会读入DER数字签名第一个字节,并判断这个字节是否为0x30,如果不是则返回错误。

pub fn read_constructed_sequence(&mut self) -> Result<(), Error> 
        let v = self.read()?;
        if v == 0x30 
            Ok(())
         else 
            Err(Error::InvalidSignature)
        
    

在read_constructed_sequence函数中读入当前一个字节通过read()函数实现,代码如下。

pub fn read(&mut self) -以上是关于libsecp256k1比特币密码算法开源库(十三)的主要内容,如果未能解决你的问题,请参考以下文章

libsecp256k1比特币密码算法开源库

libsecp256k1比特币密码算法开源库

libsecp256k1比特币密码算法开源库

libsecp256k1比特币密码算法开源库

libsecp256k1比特币密码算法开源库

libsecp256k1比特币密码算法开源库