2021SC@SDUSC
secp256k1的结构——数字签名(上)
- DER签名(上)
- 数字签名Signature
- 数字签名反序列化
- DER数字签名反序列化
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[total−length]0x02[R−length][R]0x02[S−length][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=k−1(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[total−length]0x02[R−length][R]0x02[S−length][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
}