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

作者: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)
    }
set_gej函数实现如下代码所示,实现的功能就是根据传入的Jacobian类型坐标点转化为Affine仿射坐标点,之前博客中提到过,Jacobian类型坐标点转化为Affine仿射坐标点的对应关系为:Jacobian射影坐标点(x,y,z)对应仿射坐标中的点(z2x​,z3y​),按照这个对应关系很容易看懂下面的代码,在相应位置我给出了注释。
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;
    }

相关文章:

发表评论

您的电子邮箱地址不会被公开。