作者:yyDrifter
时间:2021年11月28日
来源:https://blog.csdn.net/qq_50680426/article/details/121445011
2021SC@SDUSC
本文将介绍secp256k1定义的公钥结构,主要介绍公钥的三种格式以及公钥相应的函数实现。
公钥解析的格式
公钥的格式下面例举了三种,分别是Compressed、Full和Raw:
pub enum PublicKeyFormat {
/// Compressed public key, 33 bytes.
Compressed,
/// Full length public key, 65 bytes.
Full,
/// Raw public key, 64 bytes.
Raw,
}
下面分别介绍这个东西代表什么意思。
Compressed public key即为压缩公钥;
Full length public key即为未压缩公钥;
Raw public key即为原始公钥。
虽然上面是这么个顺序,但是介绍的话需要反过来介绍。
Raw public key原始公钥
先说Raw public key原始公钥,这其实就是最先认识的那个公钥,它用64字节即512比特表示,由于公钥点坐标的横纵坐标都是256位比特32字节,这个64字节的公钥其实就是把横纵坐标直接拼起来。
这样表示公钥的话省时省力,公钥是什么样就表示成为什么样。但是有一个用于序列化ECDSA的公钥标准,称之为Standard for Efficient Cryptography(SEC高效加密标准),那么既然他说高效了,直接原样存储公钥肯定不是最高效的,于是就有了未压缩公钥和压缩公钥。
Full length public key未压缩公钥
未压缩公钥有65个字节,比原始公钥还要多一个字节,这其实是为了区分未压缩公钥和压缩公钥,给他们分别加一个一字节的前缀。未压缩公钥的一字节前缀用16进制表示为0x04,由于是16进制表示,04代表两个16进制数,共8比特,即一字节。
Compressed public key压缩公钥
压缩公钥有33字节,和未压缩公钥一样,在压缩公约前面加了一个字节的前缀来表示是压缩后的公钥,剩下的32字节就只有公钥的x坐标,y坐标不再保留,要得到公钥的y坐标需要把x坐标代入到椭圆曲线方程中得到。
但是这里就会有一个问题,有限域椭圆曲线方程关于y=p/2对称,如果只给一个x坐标会得到两个y坐标值,且这两个y坐标值的和为p。这样就需要对具体是哪个y来标记,由于两个y坐标值的和为p,那么必定有一个y是奇数,另一个是偶数。规定一字节前缀如果为0x02则表示y为偶数,0x03表示y为奇数,这样就可以使用1字节前缀+32字节x坐标来表示压缩后的33字节公钥了。
公钥PublicKey
结构体PublicKey表示公钥,公钥就是一个仿射坐标点:
pub struct PublicKey(Affine);
公钥实现的函数如下图所示,相比Message而言多了很多,下文将会具体分析,这里先把细节抹掉,关注整体实现:
impl PublicKey {
//公钥生成:
pub fn from_secret_key_with_context(seckey: &SecretKey,
context: &ECMultGenContext,) -> PublicKey {
...
}
#[cfg(any(feature = "static-context", feature = "lazy-static-context"))]
pub fn from_secret_key(seckey: &SecretKey) -> PublicKey {
...
}
//公钥反序列化
pub fn parse_slice(p: &[u8], format: Option) -> Result<PublicKey, Error> {
...
}
pub fn parse(p: &[u8; util::FULL_PUBLIC_KEY_SIZE]) -> Result<PublicKey, Error> {...
}
pub fn parse_compressed(
p: &[u8; util::COMPRESSED_PUBLIC_KEY_SIZE],
) -> Result<PublicKey, Error> {
...
}
//公钥序列化
pub fn serialize(&self) -> [u8; util::FULL_PUBLIC_KEY_SIZE] {
...
}
pub fn serialize_compressed(&self) -> [u8; util::COMPRESSED_PUBLIC_KEY_SIZE] {
...
}
}
下面开始讲述具体的函数实现:
公钥生成函数
公钥生成过程很简单,就是根据椭圆曲线群的生成元G,与私钥进行标量乘法运算,得到的坐标点就是公钥,这里就是公钥生成函数,实现上述过程。
这个函数表示根据context上下文生成公钥,这里的context上下文其实就是ECMultGenContext,即专门计算aG类型运算的那个impl,ecmult_gen函数应该看到比较熟悉,在libsecp256k1比特币密码算法开源库(九)的ECMultGenContext部分介绍过这个函数,就是根据传入的Jacobian类型的坐标点pj和Scalar类型的标量seckey进行标量乘法运算,得到结果点坐标。由于最后结果点坐标还是Jacobian类型,需要得到的公钥是一个仿射坐标点,需要用set_gej函数将Jacobian坐标点转化为Affine仿射坐标点。
pub fn from_secret_key_with_context(
seckey: &SecretKey,
context: &ECMultGenContext,
) -> PublicKey {
let mut pj = Jacobian::default();
context.ecmult_gen(&mut pj, &seckey.0);
let mut p = Affine::default();
p.set_gej(&pj);
PublicKey(p)
}
pub fn set_gej(&mut self, a: &Jacobian) {
self.infinity = a.infinity;
let mut a = *a;
a.z = a.z.inv();//对z求逆,相当于1/z
let z2 = a.z.sqr();//对z逆求平方
let z3 = a.z * z2;//对z逆求三次方
a.x *= z2;//a.x即为x/z^2,即为仿射坐标横坐标点
a.y *= z3;//a.y即为y/z^3,即为Jacobian射影坐标纵坐标点
a.z.set_int(1);
self.x = a.x;
self.y = a.y;
}
这个函数没啥好说的,就是把上面那个函数给封装了一下,直接调用上面的那个函数from_secret_key_with_context:
pub fn from_secret_key(seckey: &SecretKey) -> PublicKey {
Self::from_secret_key_with_context(seckey, &ECMULT_GEN_CONTEXT)
}
反序列化
这个函数运行实现的就是把一个序列化的公钥给反序列化,并加入了错误处理机制。具体的反序列化函数prase(处理未压缩公钥)和parse_compressed(处理压缩公钥)的实现在这段代码的后面给出了解释。
在本文最开始的部分介绍了原始公钥、压缩公钥和未压缩公钥,这段代码的在反序列化操作前先识别传入的公钥p是哪种类型,对原始公钥做得很绝,直接返回错误了,但是在后面又把原始公钥给转化成了未压缩公钥;对于压缩公钥和未压缩公钥都是直接反序列化为PublicKey,不报错。
pub fn parse_slice(p: &[u8], format: Option) -> Result<PublicKey, Error> {
//识别公钥类型
let format = match (p.len(), format) {
(util::FULL_PUBLIC_KEY_SIZE, None)
| (util::FULL_PUBLIC_KEY_SIZE, Some(PublicKeyFormat::Full)) => PublicKeyFormat::Full,
(util::COMPRESSED_PUBLIC_KEY_SIZE, None)
| (util::COMPRESSED_PUBLIC_KEY_SIZE, Some(PublicKeyFormat::Compressed)) => {
PublicKeyFormat::Compressed
}
(util::RAW_PUBLIC_KEY_SIZE, None)
| (util::RAW_PUBLIC_KEY_SIZE, Some(PublicKeyFormat::Raw)) => PublicKeyFormat::Raw,
_ => return Err(Error::InvalidInputLength),//原始公钥返回异常
};
//反序列化公钥
match format {
//未压缩公钥使用反序列化函数prase反序列化
PublicKeyFormat::Full => {
let mut a = [0; util::FULL_PUBLIC_KEY_SIZE];
a.copy_from_slice(p);
Self::parse(&a)
}
//将原始公钥转化为未压缩公钥并反序列化
PublicKeyFormat::Raw => {
use util::TAG_PUBKEY_FULL;
let mut a = [0; util::FULL_PUBLIC_KEY_SIZE];
a[0] = TAG_PUBKEY_FULL;
a[1..].copy_from_slice(p);
Self::parse(&a)
}
//压缩公钥使用反序列化函数parse_compressed反序列化
PublicKeyFormat::Compressed => {
let mut a = [0; util::COMPRESSED_PUBLIC_KEY_SIZE];
a.copy_from_slice(p);
Self::parse_compressed(&a)
}
}
}
下面是专门针对未压缩公钥的反序列化函数prase实现过程,主要思路就是调用set_b32函数分别将x和y进行反序列化处理,其中在代码段
if !x.set_b32(array_ref!(p, 1, 32))
中,(p, 1, 32)表示在数组p中截取下标index为1-32的部分,其中1表示index下标,32表示偏移量offset,共32个数组元素,刚好表示x坐标;同样地在代码段
if !y.set_b32(array_ref!(p, 33, 32))
中,33表示index下标,32表示偏移量offset,即截取数组下标33-64的数组元素,刚好表示y坐标。
pub fn parse(p: &[u8; util::FULL_PUBLIC_KEY_SIZE]) -> Result {
use util::{TAG_PUBKEY_FULL, TAG_PUBKEY_HYBRID_EVEN, TAG_PUBKEY_HYBRID_ODD};
if !(p[0] == TAG_PUBKEY_FULL
|| p[0] == TAG_PUBKEY_HYBRID_EVEN
|| p[0] == TAG_PUBKEY_HYBRID_ODD)
{
return Err(Error::InvalidPublicKey);
}
let mut x = Field::default();
let mut y = Field::default();
//反序列化处理x坐标和y坐标
if !x.set_b32(array_ref!(p, 1, 32)) {
return Err(Error::InvalidPublicKey);
}
//将处理结束后的坐标转换为Affine类型的变量
if !y.set_b32(array_ref!(p, 33, 32)) {
return Err(Error::InvalidPublicKey);
}
let mut elem = Affine::default();
elem.set_xy(&x, &y);
if (p[0] == TAG_PUBKEY_HYBRID_EVEN || p[0] == TAG_PUBKEY_HYBRID_ODD)
&& (y.is_odd() != (p[0] == TAG_PUBKEY_HYBRID_ODD))
{
return Err(Error::InvalidPublicKey);
}
if elem.is_infinity() {
return Err(Error::InvalidPublicKey);
}
if elem.is_valid_var() {
Ok(PublicKey(elem))
} else {
Err(Error::InvalidPublicKey)
}
}
截取好对应部分后分别传入set_b32函数进行反序列化处理得到反序列化的x坐标和y坐标。注意这里的set_b32和Message那篇的set_b32虽然名字一样,但是实现不一样,这是因为Message实际上就是一个scalar(即标量),它只有256字节,因此需要8个u32数组元素;但是公钥是一个Field,在压缩前它有320字节(压缩存储之后才会变成256字节),因此需要10个u32数组元素接收。在下面的代码中你可以看到使用了 self.n[0] ~ self.n[9]共计10个数组元素。
#[must_use]
pub fn set_b32(&mut self, a: &[u8; 32]) -> bool {
self.n[0] = (a[31] as u32)
| ((a[30] as u32) << 8)
| ((a[29] as u32) << 16)
| (((a[28] & 0x3) as u32) << 24);
self.n[1] = (((a[28] >> 2) & 0x3f) as u32)
| ((a[27] as u32) << 6)
| ((a[26] as u32) << 14)
| (((a[25] & 0xf) as u32) << 22);
self.n[2] = (((a[25] >> 4) & 0xf) as u32)
| ((a[24] as u32) << 4)
| ((a[23] as u32) << 12)
| (((a[22] as u32) & 0x3f) << 20);
self.n[3] = (((a[22] >> 6) & 0x3) as u32)
| ((a[21] as u32) << 2)
| ((a[20] as u32) << 10)
| ((a[19] as u32) << 18);
self.n[4] = (a[18] as u32)
| ((a[17] as u32) << 8)
| ((a[16] as u32) << 16)
| (((a[15] & 0x3) as u32) << 24);
self.n[5] = (((a[15] >> 2) & 0x3f) as u32)
| ((a[14] as u32) << 6)
| ((a[13] as u32) << 14)
| (((a[12] as u32) & 0xf) << 22);
self.n[6] = (((a[12] >> 4) & 0xf) as u32)
| ((a[11] as u32) << 4)
| ((a[10] as u32) << 12)
| (((a[9] & 0x3f) as u32) << 20);
self.n[7] = (((a[9] >> 6) & 0x3) as u32)
| ((a[8] as u32) << 2)
| ((a[7] as u32) << 10)
| ((a[6] as u32) << 18);
self.n[8] = (a[5] as u32)
| ((a[4] as u32) << 8)
| ((a[3] as u32) << 16)
| (((a[2] & 0x3) as u32) << 24);
self.n[9] = (((a[2] >> 2) & 0x3f) as u32) | ((a[1] as u32) << 6) | ((a[0] as u32) << 14);
if self.n[9] == 0x03fffff
&& (self.n[8] & self.n[7] & self.n[6] & self.n[5] & self.n[4] & self.n[3] & self.n[2])
== 0x3ffffff
&& (self.n[1] + 0x40 + ((self.n[0] + 0x3d1) >> 26)) > 0x3ffffff
{
return false;
}
self.magnitude = 1;
self.normalized = true;
debug_assert!(self.verify());
true
}
最后创建一个Affine类型的变量,调用set_xy函数接收x坐标和y坐标,实现将序列化数组p转化为了一个Affine类型的变量elem,实现了反序列化的过程。
set_xy函数实现如下所示,即创建一个与给定x和y坐标对应的椭圆曲线群元素,且坐标为仿射坐标。
pub fn set_xy(&mut self, x: &Field, y: &Field) {
self.infinity = false;
self.x = *x;
self.y = *y;
}
下面是专门针对压缩公钥的反序列化函数parse_compressed实现过程,压缩的公钥没有y坐标只有x坐标,因此只对x坐标使用set_b32函数处理即可,得到反序列化的x坐标。
最后创建一个Affine类型的变量,调用set_xo_var函数接收x坐标,实现将序列化数组p转化为了一个Affine类型的变量elem,实现了反序列化的过程。
pub fn parse_compressed(
p: &[u8; util::COMPRESSED_PUBLIC_KEY_SIZE],
) -> Result {
use util::{TAG_PUBKEY_EVEN, TAG_PUBKEY_ODD};
if !(p[0] == TAG_PUBKEY_EVEN || p[0] == TAG_PUBKEY_ODD) {
return Err(Error::InvalidPublicKey);
}
let mut x = Field::default();
//反序列化处理x坐标
if !x.set_b32(array_ref!(p, 1, 32)) {
return Err(Error::InvalidPublicKey);
}
//将处理结束后的坐标转换为Affine类型的变量
let mut elem = Affine::default();
elem.set_xo_var(&x, p[0] == TAG_PUBKEY_ODD);
if elem.is_infinity() {
return Err(Error::InvalidPublicKey);
}
if elem.is_valid_var() {
Ok(PublicKey(elem))
} else {
Err(Error::InvalidPublicKey)
}
}
在传入set_xo_var时就设置p[0] == TAG_PUBKEY_ODD
,即公钥y坐标为奇数。
在set_xo_var函数中实现设置一个仿射坐标表示的群元素,这个群元素的x坐标与给定x坐标一致。
在set_xo_var函数中调用函数set_xquad,实现群元素坐标x和y的设置,设置之外还是为了验证给定的x坐标是不是有效,即将x代入到椭圆曲线方程中能否找到一个对应的y点。
pub fn set_xo_var(&mut self, x: &Field, odd: bool) -> bool {
if !self.set_xquad(x) {
return false;
}
//将在set_xquad中得到的y坐标正常化
self.y.normalize_var();
//判断在set_xquad中得到的y坐标是否为奇数,
//如果不是奇数使用neg函数得到对应奇数坐标
if self.y.is_odd() != odd {
self.y = self.y.neg(1);
}
true
}
函数set_xquad实现设置一个仿射坐标表示的群元素,这个群元素的x坐标等于给定x坐标,并求出这个x坐标对应椭圆曲线方程的纵坐标y。如果存在一个给定x坐标的椭圆曲线群下的坐标,则返回值为真。
pub fn set_xquad(&mut self, x: &Field) -> bool {
self.x = *x;
let x2 = x.sqr();
let x3 = *x * x2;//求x的三次方
self.infinity = false;
let mut c = Field::default();
c.set_int(CURVE_B);
c += x3;//求 x的三次方 加 b
let (v, ret) = c.sqrt();//求平方剩余,即求一个数v,v的平方模p结果为c
self.y = v;//求得的v即为纵坐标y
ret
}
序列化
本代码实现将未压缩公钥序列化。具体的序列化实现通过调用fill_b32函数实现,对x和y分别使用fill_b32函数,将坐标序列化。
pub fn serialize(&self) -> [u8; util::FULL_PUBLIC_KEY_SIZE] {
use util::TAG_PUBKEY_FULL;
debug_assert!(!self.0.is_infinity());
let mut ret = [0u8; 65];
let mut elem = self.0;
elem.x.normalize_var();
elem.y.normalize_var();
elem.x.fill_b32(array_mut_ref!(ret, 1, 32));
elem.y.fill_b32(array_mut_ref!(ret, 33, 32));
ret[0] = TAG_PUBKEY_FULL;
ret
}
fill_b32实现过程如下所示,Field元素的公钥一个x或y坐标包括10个u32数组元素,序列化后包括32个字节,用32个u8数组元素接收,实现序列化过程。
pub fn fill_b32(&self, r: &mut [u8; 32]) {
debug_assert!(self.normalized);
debug_assert!(self.verify());
r[0] = ((self.n[9] >> 14) & 0xff) as u8;
r[1] = ((self.n[9] >> 6) & 0xff) as u8;
r[2] = (((self.n[9] & 0x3f) << 2) | ((self.n[8] >> 24) & 0x3)) as u8;
r[3] = ((self.n[8] >> 16) & 0xff) as u8;
r[4] = ((self.n[8] >> 8) & 0xff) as u8;
r[5] = (self.n[8] & 0xff) as u8;
r[6] = ((self.n[7] >> 18) & 0xff) as u8;
r[7] = ((self.n[7] >> 10) & 0xff) as u8;
r[8] = ((self.n[7] >> 2) & 0xff) as u8;
r[9] = (((self.n[7] & 0x3) << 6) | ((self.n[6] >> 20) & 0x3f)) as u8;
r[10] = ((self.n[6] >> 12) & 0xff) as u8;
r[11] = ((self.n[6] >> 4) & 0xff) as u8;
r[12] = (((self.n[6] & 0xf) << 4) | ((self.n[5] >> 22) & 0xf)) as u8;
r[13] = ((self.n[5] >> 14) & 0xff) as u8;
r[14] = ((self.n[5] >> 6) & 0xff) as u8;
r[15] = (((self.n[5] & 0x3f) << 2) | ((self.n[4] >> 24) & 0x3)) as u8;
r[16] = ((self.n[4] >> 16) & 0xff) as u8;
r[17] = ((self.n[4] >> 8) & 0xff) as u8;
r[18] = (self.n[4] & 0xff) as u8;
r[19] = ((self.n[3] >> 18) & 0xff) as u8;
r[20] = ((self.n[3] >> 10) & 0xff) as u8;
r[21] = ((self.n[3] >> 2) & 0xff) as u8;
r[22] = (((self.n[3] & 0x3) << 6) | ((self.n[2] >> 20) & 0x3f)) as u8;
r[23] = ((self.n[2] >> 12) & 0xff) as u8;
r[24] = ((self.n[2] >> 4) & 0xff) as u8;
r[25] = (((self.n[2] & 0xf) << 4) | ((self.n[1] >> 22) & 0xf)) as u8;
r[26] = ((self.n[1] >> 14) & 0xff) as u8;
r[27] = ((self.n[1] >> 6) & 0xff) as u8;
r[28] = (((self.n[1] & 0x3f) << 2) | ((self.n[0] >> 24) & 0x3)) as u8;
r[29] = ((self.n[0] >> 16) & 0xff) as u8;
r[30] = ((self.n[0] >> 8) & 0xff) as u8;
r[31] = (self.n[0] & 0xff) as u8;
}
压缩公钥序列化实现同理,由于是压缩公钥只需序列化x坐标即可,重新调用上面的fill_b32函数实现序列化过程。最后对y坐标的奇偶性判断,加上对应的压缩前缀。
pub fn fill_b32(&self, r: &mut [u8; 32]) {
debug_assert!(self.normalized);
debug_assert!(self.verify());
r[0] = ((self.n[9] >> 14) & 0xff) as u8;
r[1] = ((self.n[9] >> 6) & 0xff) as u8;
r[2] = (((self.n[9] & 0x3f) << 2) | ((self.n[8] >> 24) & 0x3)) as u8;
r[3] = ((self.n[8] >> 16) & 0xff) as u8;
r[4] = ((self.n[8] >> 8) & 0xff) as u8;
r[5] = (self.n[8] & 0xff) as u8;
r[6] = ((self.n[7] >> 18) & 0xff) as u8;
r[7] = ((self.n[7] >> 10) & 0xff) as u8;
r[8] = ((self.n[7] >> 2) & 0xff) as u8;
r[9] = (((self.n[7] & 0x3) << 6) | ((self.n[6] >> 20) & 0x3f)) as u8;
r[10] = ((self.n[6] >> 12) & 0xff) as u8;
r[11] = ((self.n[6] >> 4) & 0xff) as u8;
r[12] = (((self.n[6] & 0xf) << 4) | ((self.n[5] >> 22) & 0xf)) as u8;
r[13] = ((self.n[5] >> 14) & 0xff) as u8;
r[14] = ((self.n[5] >> 6) & 0xff) as u8;
r[15] = (((self.n[5] & 0x3f) << 2) | ((self.n[4] >> 24) & 0x3)) as u8;
r[16] = ((self.n[4] >> 16) & 0xff) as u8;
r[17] = ((self.n[4] >> 8) & 0xff) as u8;
r[18] = (self.n[4] & 0xff) as u8;
r[19] = ((self.n[3] >> 18) & 0xff) as u8;
r[20] = ((self.n[3] >> 10) & 0xff) as u8;
r[21] = ((self.n[3] >> 2) & 0xff) as u8;
r[22] = (((self.n[3] & 0x3) << 6) | ((self.n[2] >> 20) & 0x3f)) as u8;
r[23] = ((self.n[2] >> 12) & 0xff) as u8;
r[24] = ((self.n[2] >> 4) & 0xff) as u8;
r[25] = (((self.n[2] & 0xf) << 4) | ((self.n[1] >> 22) & 0xf)) as u8;
r[26] = ((self.n[1] >> 14) & 0xff) as u8;
r[27] = ((self.n[1] >> 6) & 0xff) as u8;
r[28] = (((self.n[1] & 0x3f) << 2) | ((self.n[0] >> 24) & 0x3)) as u8;
r[29] = ((self.n[0] >> 16) & 0xff) as u8;
r[30] = ((self.n[0] >> 8) & 0xff) as u8;
r[31] = (self.n[0] & 0xff) as u8;
}