IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 区块链 -> libsecp256k1比特币密码算法开源库(九) -> 正文阅读

[区块链]libsecp256k1比特币密码算法开源库(九)

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=u1?G+u2?HA?,这其实就是一个 a P + b G aP + bG aP+bG类型的运算;在公钥恢复中还有一步 H A = ( s Q ? z G ) r ? 1 H_A = (sQ - zG)r^{-1} HA?=(sQ?zG)r?1,实际上就是计算 ( s r ? 1 ) Q ? ( z r ? 1 ) G (sr^{-1})Q - (zr^{-1})G (sr?1)Q?(zr?1)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?=s?1zmodnu2?=s?1rmodnQ=u1?G+u2?HA?rQ?=xQ?modnrQ?=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 u1?Gu2?HA?传入即可得到点 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?=(sQ?zG)r?1得到公钥。

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

得到u1和u2就可以和签名验证过程使用的ecmult一样,计算 H A = ( s r ? 1 ) Q ? ( z r ? 1 ) G H_A =(sr^{-1})Q - (zr^{-1})G HA?=(sr?1)Q?(zr?1)G,即 H A = u 2 Q ? u 1 G H_A =u_2Q - u_1G HA?=u2?Q?u1?G,把参数传入函数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=xP?modns=k?1(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结构定义之两种运算类型的实现

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

  区块链 最新文章
盘点具备盈利潜力的几大加密板块,以及潜在
阅读笔记|让区块空间成为商品,打造Web3云
区块链1.0-比特币的数据结构
Team Finance被黑分析|黑客自建Token“瞒天
区块链≠绿色?波卡或成 Web3“生态环保”标
期货从入门到高深之手动交易系列D1课
以太坊基础---区块验证
进入以太坊合并的五个数字
经典同态加密算法Paillier解读 - 原理、实现
IPFS/Filecoin学习知识科普(四)
上一篇文章           查看所有文章
加:2021-11-16 18:52:46  更:2021-11-16 18:54:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 19:04:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计