secp256k1曲线Curve结构定义——两种运算类型的实现

2021SC@SDUSC

secp256k1曲线Curve结构定义——两种运算类型的实现

libsecp256k1比特币密码算法开源库(五)中介绍了签名验证、公钥恢复和数字签名的数理逻辑,在具体代码实现中对 a P + b G aP + bG aP+bG类型和 a G aG aG类型(即标题中的两种运算类型)的运算进行了一定的提速。

在数理逻辑中曾经介绍过,签名验证和公钥恢复中有一步需要计算一个点 Q Q Q,计算过程为 Q = u 1 G + u 2 H A Q=u_1G+u_2H_A Q=u1G+u2HA,这其实就是一个 a P + b G aP + bG aP+bG类型的运算;在公钥恢复中还有一步 H A = ( s Q − z G ) r − 1 H_A = (sQ - zG)r^{-1} HA=(sQzG)r1,实际上就是计算 ( s r − 1 ) Q − ( z r − 1 ) G (sr^{-1})Q - (zr^{-1})G (sr1)Q(zr1)G,显然也是 a P + b G aP + bG aP+bG类型的运算;在数字签名中有一步需要计算 P = k G P=kG P=kG,这是一个 a G aG aG类型的运算。在ECMultContext中使用了ecmult函数实现了 a P + b G aP + bG aP+bG的高效运算,提高了签名验证和公钥恢复的效率;在ECMultGenContext中使用了ecmult_gen函数实现了 a G aG aG的高效运算。

由于代码中取的变量名和数学表达式中有些不一致,我会在代码片前介绍清除对应关系,由于在代码片中不能使用LaTex数学公式,可能变量脚标会看的比较混乱,建议比对代码片前的文字。

Scalar

Scalar为256位标量值,使用8个32位的数组元素进行表示。secp256k1中多处用到标量scalar,其中私钥和数字签名中使用的随机数 k k k等都是标量,此外在本文中还会见到很多运算的中间量也是scalar标量

pub struct Scalar(pub [u32; 8]);

ECMultContext

在ECMultContext中使用了一个ecmult函数,这个函数可以加快 a P + b G aP + bG aP+bG类型的运算的速度,其中a和b都是Scalar类型标量,P和G都是Jacobian坐标点。具体ecmult函数实现代码如下所示:

pub fn ecmult(&self, r: &mut Jacobian, a: &Jacobian, na: &Scalar, ng: &Scalar) {
        let mut tmpa = Affine::default();
        let mut pre_a: [Affine; ECMULT_TABLE_SIZE_A] = Default::default();
        let mut z = Field::default();
        let mut wnaf_na = [0i32; 256];
        let mut wnaf_ng = [0i32; 256];
        let bits_na = ecmult_wnaf(&mut wnaf_na, na, WINDOW_A);
        let mut bits = bits_na;
        odd_multiples_table_globalz_windowa(&mut pre_a, &mut z, a);

        let bits_ng = ecmult_wnaf(&mut wnaf_ng, &ng, WINDOW_G);
        if bits_ng > bits {
            bits = bits_ng;
        }

        r.set_infinity();
        for i in (0..bits).rev() {
            let mut n;
            *r = r.double_var(None);

            n = wnaf_na[i as usize];
            if i < bits_na && n != 0 {
                table_get_ge(&mut tmpa, &pre_a, n, WINDOW_A);
                *r = r.add_ge_var(&tmpa, None);
            }
            n = wnaf_ng[i as usize];
            if i < bits_ng && n != 0 {
                table_get_ge_storage(&mut tmpa, &self.pre_g, n, WINDOW_G);
                *r = r.add_zinv_var(&tmpa, &z);
            }
        }

        if !r.is_infinity() {
            r.z *= &z;
        }
    }

在签名验证和公钥恢复中都有形如aP + bG的运算,既然实现了ecmult函数,就可以在ECMultContext中使用这个函数加速签名验证和公钥恢复中的相关运算步骤。

签名验证verify

下面是签名验证的数学步骤:
u 1 = s − 1 z   m o d   n u 2 = s − 1 r   m o d   n Q = u 1 G + u 2 H A r Q = x Q   m o d   n 验证 r Q = r   是否成立 u_1=s^{-1}z\,mod\,n\\u_2=s^{-1}r\,mod\,n\\Q=u_1G+u_2H_A\\r_Q=x_Q\,mod\,n\\验证r_Q=r\,是否成立 u1=s1zmodnu2=s1rmodnQ=u1G+u2HArQ=xQmodn验证rQ=r是否成立在下面的代码中,
sigr表示数字签名的r部分,即数学表达式中的 r r r
sigs表示数字签名的s部分,即数学表达式中的 s s s
pubkey表示公钥 H A H_A HA
message表示信息摘要(即信息被SHA256哈希后的结果) z z z

只要使用ecmult函数,将相应参数 u 1 、 G 、 u 2 、 H A u_1、G、u_2、H_A u1Gu2HA传入即可得到点 Q Q Q,下面是相关代码实现:

impl ECMultContext {
    pub fn verify_raw(
        &self,
        sigr: &Scalar,
        sigs: &Scalar,
        pubkey: &Affine,
        message: &Scalar,//此处message为哈希之后的结果 使用SHA-256,类型为256位Scalar
    ) -> bool {
        let c;
        let (sn, u1, u2): (Scalar, Scalar, Scalar);

        if sigr.is_zero() || sigs.is_zero() {
            return false;
        }

        sn = sigs.inv_var();//求出签名的s部分的乘法逆元
        u1 = &sn * message;
        u2 = &sn * sigr;
        let mut pubkeyj: Jacobian = Jacobian::default();
        pubkeyj.set_ge(pubkey);
        let mut pr: Jacobian = Jacobian::default();
        self.ecmult(&mut pr, &pubkeyj, &u2, &u1);//ecmult函数
        //上面一条代码即为计算u1G+u2HA,得到的结果Q点返还给pr
        if pr.is_infinity() {
            return false;
        }//得到的Q点不能为无穷远点

        c = sigr.b32();//c值为签名的r部分
        let mut xr: Field = Default::default();
        let _ = xr.set_b32(&c);

        if pr.eq_x_var(&xr) {
            return true;
        }//判断取模后的Q点横坐标是否与签名的r部分相同,如果相同则表示签名验证成功
        if xr >= P_MINUS_ORDER {
            return false;
        }
        xr += ORDER_AS_FE;
        if pr.eq_x_var(&xr) {
            return true;
        }
        false
    }
}

公钥恢复recover

公钥恢复原理证明之前已经写过了,这里直接写公钥恢复的步骤:

先将数字签名的 r r r部分代入到椭圆曲线方程中得到一个纵坐标,将 r r r作为横坐标,在方程中得到的纵坐标,组合成一个点 Q Q Q

计算 H A = ( s Q − z G ) r − 1 H_A = (sQ - zG)r^{-1} HA=(sQzG)r1得到公钥。

在下面的代码中,
rn表示r的逆元 r − 1 r^{-1} r1
u1表示 z r − 1 zr^{-1} zr1
u2表示 s r − 1 sr^{-1} sr1

得到u1和u2就可以和签名验证过程使用的ecmult一样,计算 H A = ( s r − 1 ) Q − ( z r − 1 ) G H_A =(sr^{-1})Q - (zr^{-1})G HA=(sr1)Q(zr1)G,即 H A = u 2 Q − u 1 G H_A =u_2Q - u_1G HA=u2Qu1G,把参数传入函数ecmult中即可。下面即为相关代码实现:

impl ECMultContext {
    pub fn recover_raw(
        &self,
        sigr: &Scalar,
        sigs: &Scalar,
        rec_id: u8,
        message: &Scalar,
    ) -> Result<Affine, Error> {
        debug_assert!(rec_id < 4);

        if sigr.is_zero() || sigs.is_zero() {
            return Err(Error::InvalidSignature);
        }

        let brx = sigr.b32();
        let mut fx = Field::default();
        let overflow = fx.set_b32(&brx);
        debug_assert!(overflow);

        if rec_id & 2 > 0 {
            if fx >= P_MINUS_ORDER {
                return Err(Error::InvalidSignature);
            }
            fx += ORDER_AS_FE;
        }
        let mut x = Affine::default();
        if !x.set_xo_var(&fx, rec_id & 1 > 0) {
            return Err(Error::InvalidSignature);
        }
        let mut xj = Jacobian::default();
        xj.set_ge(&x);
        let rn = sigr.inv();//求r的逆
        let mut u1 = &rn * message;//计算标量运算z和r逆的积
        u1 = -u1;//取负值
        let u2 = &rn * sigs;//计算标量运算s和r逆的积
        let mut qj = Jacobian::default();
        self.ecmult(&mut qj, &xj, &u2, &u1);//ecmult函数

        let mut pubkey = Affine::default();
        pubkey.set_gej_var(&qj);

        if pubkey.is_infinity() {
            Err(Error::InvalidSignature)
        } else {
            Ok(pubkey)
        }
    }
}

ECMultGenContext

ECMultGenContext 中实现了 a G aG aG类型的快速计算,实际上只有签名过程的 P = k G P=kG P=kG使用了这种类型的运算,在代码中为 a G aG aG类型的运算定义了一个ecmult_gen函数,实现了对 a G aG aG类型的快速计算。在代码中r是Jacobian类型的坐标点,gn是scalar标量,具体ecmult_gen函数实现代码如下所示:

pub fn ecmult_gen(&self, r: &mut Jacobian, gn: &Scalar) {
        let mut adds = AffineStorage::default();
        *r = self.initial;

        let mut gnb = gn + &self.blind;
        let mut add = Affine::default();
        add.infinity = false;

        for j in 0..64 {
            let mut bits = gnb.bits(j * 4, 4);
            for i in 0..16 {
                adds.cmov(&self.prec[j][i], i as u32 == bits);
            }
            add = adds.into();
            *r = r.add_ge(&add);
            #[allow(unused_assignments)]
            {
                bits = 0;
            }
        }
        add.clear();
        gnb.clear();
    }

签名sign

签名的步骤:
P = k G r = x P   m o d   n s = k − 1 ( z + r d A )   m o d   n P=kG\\r=x_P\, mod \, n\\s=k^{-1}(z+rd_A) \,mod \,n P=kGr=xPmodns=k1(z+rdA)modn在下面的代码中,
seckey表示私钥 d A d_A dA
nonce表示随机数 k k k
n表示 z + r d A z+rd_A z+rdA
sigr表示数字签名的r部分,即数学表达式中的 r r r
sigs表示数字签名的s部分,即数学表达式中的 s s s

结合我给下面代码中的相应注释,很容易理解相关运算的实现。值得一提的是Rust可以说就是为极致追求安全性的场景准备的一个语言,它可以很方便地管理内存,及时将敏感信息在内存中抹掉。比如在数字签名中随机数 k k k值和私钥 d A d_A dA就是非常敏感的信息,在之前我也写过为什么 k k k值不能泄露,即 k k k值泄露就意味着私钥泄露,同时 k k k值也不能重复使用,重复使用的结果也是 k k k值泄露,从而导致私钥泄露。既然如此还是把 k k k值尽快释放掉为好。在这里Rust为这种敏感信息的内存释放提供了很大的方便。

impl ECMultGenContext {
    pub fn sign_raw(
        &self,
        seckey: &Scalar,
        message: &Scalar,
        nonce: &Scalar,
    ) -> Result<(Scalar, Scalar, u8), Error> {
        let mut rp = Jacobian::default();
        self.ecmult_gen(&mut rp, nonce);//ecmult_gen函数
        let mut r = Affine::default();
        r.set_gej(&rp);//得到签名的r部分
        r.x.normalize();
        r.y.normalize();
        let b = r.x.b32();
        let mut sigr = Scalar::default();
        let overflow = bool::from(sigr.set_b32(&b));
        debug_assert!(!sigr.is_zero());
        debug_assert!(!overflow);

        let mut recid = (if overflow { 2 } else { 0 }) | (if r.y.is_odd() { 1 } else { 0 });
        let mut n = &sigr * seckey;//计算r*dA
        n += message;//计算z+r*dA
        let mut sigs = nonce.inv();//计算nonce(即k)的逆元
        sigs *= &n;//得到签名的s部分
        n.clear();
        rp.clear();
        r.clear();//释放清理内存,防止关键信息泄露
        if sigs.is_zero() {
            return Err(Error::InvalidMessage);
        }
        if sigs.is_high() {
            sigs = -sigs;
            recid ^= 1;
        }
        Ok((sigr, sigs, recid))//将r和s拼接在一起形成最终的数字签名
    }
}

到本篇为止,曲线Curve结构定义已经介绍完毕,你可以参考本文和之前的两篇对曲线的结构有一定的了解。
libsecp256k1比特币密码算法开源库(七)——secp256k1曲线Curve结构定义之坐标转换
libsecp256k1比特币密码算法开源库(八)——secp256k1曲线Curve结构定义之域元素的运算与压缩存储
以及本文
libsecp256k1比特币密码算法开源库(九)——secp256k1曲线Curve结构定义之两种运算类型的实现

后面将陆续介绍信息哈希摘要生成、公钥、私钥、数字签名等内容。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值