secp256k1的结构——数字签名(上)

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) -> Result<u8, Error> {
        if self.1 >= self.0.len() {
            Err(Error::InvalidSignature)
        } else {
            let v = self.0[self.1];
            self.1 += 1;
            Ok(v)
        }
    }

在DER数字签名中0x30字段之后就是[total-length]字段,在这里调用了read_len函数来读取该字段的内容。但是注意在DER编码中除了[total-length]字段表示数字签名的长度外,[R-length]表示r字段的长度,[S-length]表示s字段的长度,在后面读到[R-length]和[S-length]字段的时候调用的其实也是read_len函数,因此在下面的代码中有些并不是只为[total-length]服务。对于r和s而言,如果r或s的第一个字节大于等于0x80,则会在r或s前置0x00。

为了表述方便,代码细节描述写在了下面的注释中。

pub fn read_len(&mut self) -> Result<usize, Error> {
//读入[total-length]或[R-length]或[S-length]字段,传给变量b1
        let b1 = self.read()?;
        if b1 == 0xff {
            return Err(Error::InvalidSignature);
        }

        //b1的最高位不能为1
        if b1 & 0x80 == 0 {
            return Ok(b1 as usize);
        }

        if b1 == 0x80 {
            return Err(Error::InvalidSignature);
        }
        //b1的最高位不能为1,则取其7位作为长度字段
        let mut lenleft = (b1 & 0x7f) as usize;
        //字段上注明的长度不能大于实际对应的长度
        //remaining_len函数可以得到表示签名或r或s的实际长度
        if lenleft > self.remaining_len() {
            return Err(Error::InvalidSignature);
        }

        if self.peek(0)? == 0 {
            //不是最短编码,返回错误
            return Err(Error::InvalidSignature);
        }

        if lenleft > mem::size_of::<usize>() {
            return Err(Error::InvalidSignature);
        }

        let mut ret = 0;
        while lenleft > 0 {
            ret = (ret << 8) | (self.read()? as usize);
            if ret + lenleft > self.remaining_len() {
                return Err(Error::InvalidSignature);
            }
            lenleft -= 1;
        }

        if ret < 128 {
            //不是最短编码,返回错误
            return Err(Error::InvalidSignature);
        }

        Ok(ret)
    }

上面的[total-length]字段结束后开始分析r和s了。对r和s的分析调用的是同一个函数即read_integer,在这个函数中完成分析 0x02 [R-length] [R] 部分或 0x02 [S-length] [S]部分,分析的是r还是s具体要看是哪个调用的这个函数,比如在parse_der中执行代码:

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

先看r再看s,下面是read_integer代码的具体实现过程:

  pub fn read_integer(&mut self) -> Result<Scalar, Error> {
  //判断第一个字节是否为0x02,如果不是则返回错误
        if self.read()? != 0x02 {
            return Err(Error::InvalidSignature);
        }
 //调用read_len函数,读入[R-length]或[S-length]字段
        let mut rlen = self.read_len()?;
        //长度字段不能为0,也不能大于实际r或s的字节长度
        if rlen == 0 || rlen > self.remaining_len() {
            return Err(Error::InvalidSignature);
        }

        if self.peek(0)? == 0x00 && rlen > 1 && (self.peek(1)? & 0x80) == 0x00 {
            return Err(Error::InvalidSignature);
        }

        if self.peek(0)? == 0xff && rlen > 1 && (self.peek(1)? & 0x80) == 0x00 {
            return Err(Error::InvalidSignature);
        }

        let mut overflow = false;
        if self.peek(0)? & 0x80 == 0x80 {
            overflow |= true;
        }

        //跳过前导零字节
        //while循环根据字节长度读入完整的[R]部分或[S]部分
        while rlen > 0 && self.peek(0)? == 0 {
            rlen -= 1;
            self.read()?;
        }

        if rlen > 32 {
            overflow |= true;
        }
        //创建标量int,接收反序列化结果
        let mut int = Scalar::default();
        //将r和s反序列化
        if !overflow {
            let mut b32 = [0u8; 32];
            b32[32 - rlen..].copy_from_slice(self.peek_slice(rlen)?);
            self.skip(rlen)?;
        //序列化调用set_b32函数实现
            overflow |= bool::from(int.set_b32(&b32));
        }

        if overflow {
            int = Scalar::default();
        }

        Ok(int)
    }

peek函数实现提前读入下一字节,字节内容保存在变量v中:

pub fn peek(&self, forward: usize) -> Result<u8, Error> {
        if self.1 + forward >= self.0.len() {
            Err(Error::InvalidSignature)
        } else {
            let v = self.0[self.1 + forward];
            Ok(v)
        }
    }

remaining_len函数计算当前字节之后的剩余字节长度:

 pub fn remaining_len(&self) -> usize {
        self.0.len() - self.1
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Java中使用secp256k1签名并恢复公钥,你可以使用Bouncy Castle库。以下是一个示例代码: ```java import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.SecureRandom; public class ECDSAExample { public static void main(String[] args) { // Generate a random private key SecureRandom random = new SecureRandom(); BigInteger privateKey = new BigInteger(256, random); // Define the secp256k1 curve parameters ECDomainParameters curve = ECNamedCurveTable.getByName("secp256k1"); // Create an EC private key object ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(privateKey, curve); // Create an EC signer object ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); // Initialize the signer with the private key signer.init(true, privateKeyParams); // Generate a random message to sign byte[] message = "Hello, world!".getBytes(); // Calculate the signature BigInteger[] signature = signer.generateSignature(message); // Print the signature System.out.println("Signature: " + Hex.toHexString(signature[0].toByteArray()) + Hex.toHexString(signature[1].toByteArray())); // Recover the public key from the signature ECPoint publicKey = signer.getPublicKey(); // Create an EC public key object ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(publicKey, curve); // Verify the signature using the recovered public key signer.init(false, publicKeyParams); boolean valid = signer.verifySignature(message, signature[0], signature[1]); System.out.println("Valid signature? " + valid); } } ``` 这个示例代码使用Bouncy Castle库生成一个随机的私钥,使用secp256k1曲线参数创建一个EC私钥对象,并使用ECDSASigner进行签名和验证。签名后,公钥从签名中恢复,并用于验证签名

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值