secp256k1的结构——ECDH密钥交换

2021SC@SDUSC

secp256k1的结构——ECDH密钥交换


在加密通信过程中可以使用Diffie Hellman密钥交换来进行密钥分发,经过密钥交换之后相当于通信双方都有了相同的密钥,可以使用对称加密的方式通信。在区块链比特币中同样有密钥交换,即Elliptic Curve Diffie Hellman,实现将比特币发送到接收方的地址。下文中将介绍相关背景知识以及对应代码实现。

ECDH地址

本部分内容摘自维基百科,略有改动。

ECDH 地址也称为隐形地址、可重复使用的支付代码、可重复使用的地址或paynyms。椭圆曲线 Diffie-Hellman (ECDH) 是一种密钥协商协议,它允许两方通过不安全的通道建立共享密钥。例如,Alice 和 Bob 可以在他们之间交流密码信息并就共享秘密达成一致,窃听者 Eve 可以看到所有他们的消息,但仍然无法计算共享密钥。(相应数学背景知识参见libsecp256k1比特币密码算法开源库(五)

通过让比特币的接收者发布一些发送者可以用来计算共享密钥的 ECDH 信息,可以在比特币context中使用该概念。这个共享的密钥成为发送者汇款到的比特币地址。接收方可以计算出相应的私钥以获取资金。

工作过程:

接收方本地生成地址私钥 q q q:
q = ECDH address privkey (generated by receiver)
接收方本地生成地址公钥 Q Q Q:
Q = q·G = ECDH address pubkey (published by receiver)

发送方本地生成nonce串 p p p:
p = nonce (generated by sender)
发送方根据nonce串 p p p生成公钥 P P P,发送给接收方:
P = p·G = nonce point (sent from sender to receiver)

发送方接收方生成共享密钥,且该共享密钥只有这双方知道:
p·Q = q.·P = p·q·G (ECDH shared secret point, known by sender and receiver but not eavesdroppers)
由于共享密钥是一个坐标点,因此经过哈希,将共享秘密点转换为标量c:
c = H(p·Q) = H(q·P) (tweak, shared secret point converted to number)

发送者使用接收者公钥和共享密钥生成的标量c计算比特币接收地址 Q ′ Q' Q,并把币送到该地址:
Q’ = Q + c·G (bitcoin pubkey, send coins here)

接收者计算自己的比特币接收地址 Q ′ Q' Q
Q’ = Q + c.G = (q + c)·G (bitcoin pubkey, watch for incoming coins)
相应的私钥 q ′ q' q用来接下来消费比特币:
q’ = q + c (bitcoin privkey, for spending incoming coins)

共享密钥ECDH

impl<D: Digest + Default> SharedSecret<D>中定义两个函数new_with_contextnew,其中函数new中也是直接调用函数new_with_context。在new_with_context中共享密钥的实现过程通过context.ecdh_raw::<D>(&pubkey.0, &seckey.0)来实现。

impl<D: Digest + Default> SharedSecret<D> {
    pub fn new_with_context(
        pubkey: &PublicKey,
        seckey: &SecretKey,
        context: &ECMultContext,
    ) -> Result<SharedSecret<D>, Error> {
        let inner = match context.ecdh_raw::<D>(&pubkey.0, &seckey.0) {
            Some(val) => val,
            None => return Err(Error::InvalidSecretKey),
        };

        Ok(SharedSecret(inner))
    }

    #[cfg(any(feature = "static-context", feature = "lazy-static-context"))]
    pub fn new(pubkey: &PublicKey, seckey: &SecretKey) -> Result<SharedSecret<D>, Error> {
        Self::new_with_context(pubkey, seckey, &ECMULT_CONTEXT)
    }
}

找到函数ecdh_raw,可以看出生成共享密钥的函数为ecmult_const(&mut res, &pt, &s)

impl ECMultContext {
    pub fn ecdh_raw<D: Digest + Default>(
        &self,
        point: &Affine,
        scalar: &Scalar,
    ) -> Option<GenericArray<u8, D::OutputSize>> {
        let mut digest: D = Default::default();

        let mut pt = *point;
        let s = *scalar;

        if s.is_zero() {
            return None;
        }

        let mut res = Jacobian::default();
        //生成共享密钥
        self.ecmult_const(&mut res, &pt, &s);
        pt.set_gej(&res);

        pt.x.normalize();
        pt.y.normalize();

        let x = pt.x.b32();
        let y = 0x02 | (if pt.y.is_odd() { 1 } else { 0 });

        digest.update(&[y]);
        digest.update(&x);
        Some(digest.finalize_reset())
    }
}

生成共享密钥的函数ecmult_const的代码如下。

pub fn ecmult_const(&self, r: &mut Jacobian, a: &Affine, scalar: &Scalar) {
        const WNAF_SIZE: usize = (WNAF_BITS + (WINDOW_A - 1) - 1) / (WINDOW_A - 1);

        let mut tmpa = Affine::default();
        let mut pre_a: [Affine; ECMULT_TABLE_SIZE_A] = Default::default();
        let mut z = Field::default();

        let mut wnaf_1 = [0i32; 1 + WNAF_SIZE];

        let sc = *scalar;
        let skew_1 = ecmult_wnaf_const(&mut wnaf_1, &sc, WINDOW_A - 1);

        //计算a的奇数倍,
        //所有的倍数都被带入相同的“分母”,这个分母存储在Z中。
        //由于secp256k1具有同构性,可以假设Z坐标为1进行所有操作,
        //使用仿射加法公式,并在最后对结果的Z坐标进行一次修正。

        r.set_ge(a);
        odd_multiples_table_globalz_windowa(&mut pre_a, &mut z, r);
        for i in 0..ECMULT_TABLE_SIZE_A {
            pre_a[i].y.normalize_weak();
        }

        /* 第一次循环迭代
        (分离出来,所以我们可以直接设置r,而不是让它从无穷大开始,
        多次加倍,然后添加它的新值) */
        let i = wnaf_1[WNAF_SIZE];
        debug_assert!(i != 0);
        table_get_ge_const(&mut tmpa, &pre_a, i, WINDOW_A);
        r.set_ge(&tmpa);

        /*剩余的循环迭代 */
        for i in (0..WNAF_SIZE).rev() {
            for _ in 0..(WINDOW_A - 1) {
                let r2 = *r;
                r.double_nonzero_in_place(&r2, None);
            }

            let n = wnaf_1[i];
            table_get_ge_const(&mut tmpa, &pre_a, n, WINDOW_A);
            debug_assert!(n != 0);
            *r = r.add_ge(&tmpa);
        }

        r.z *= &z;

        /* 正确的wNAF倾斜 */
        let mut correction = *a;
        let mut correction_1_stor: AffineStorage;
        let a2_stor: AffineStorage;
        let mut tmpj = Jacobian::default();
        tmpj.set_ge(&correction);
        tmpj = tmpj.double_var(None);
        correction.set_gej(&tmpj);
        correction_1_stor = (*a).into();
        a2_stor = correction.into();

        /* 对于奇数,这是2a(所以替换它)对于偶数,这是a(所以没有运算) */
        correction_1_stor.cmov(&a2_stor, skew_1 == 2);

        /* 应用修正 */
        correction = correction_1_stor.into();
        correction = correction.neg();
        *r = r.add_ge(&correction)
    }

共享密钥的函数ecmult_const中主要使用如下函数:
ecmult_wnaf_constset_geodd_multiples_table_globalz_windowa
table_get_ge_constdouble_nonzero_in_placeadd_ge
下面分别介绍。
ecmult_wnaf_const

pub fn ecmult_wnaf_const(wnaf: &mut [i32], a: &Scalar, w: usize) -> i32 {
    let mut s = *a;
    let mut word = 0;

    /* 请注意,我们不能像在其他实现中那样,通过将偶数求逆为奇数来处理偶数,
    因为如果我们的标量由于性能原因被指定为宽度 < 256,
    那么它们的逆的宽度将会是256,这样我们就失去了任何性能优势。
    相反,代码使用了来自Okeya/Tagaki论文第4.2节的一种技术,
    即在我们编码的数字上加1(偶数)或2(奇数),返回一个表明这一点的倾斜值,
    并让调用者在完成乘法后进行补偿。 */

    /* 负数将被求逆,以保持其位表示低于最大宽度 */
    let flip = s.is_high();
    /* 给偶数加1,给奇数加2,注意到否定会翻转奇偶性 */
    let bit = flip ^ !s.is_even();
    /* 给偶数加1,给奇数加2,注意到否定会翻转奇偶性 */
    let neg_s = -s;
    let not_neg_one = !neg_s.is_one();
    s.cadd_bit(if bit { 1 } else { 0 }, not_neg_one);
    /* 如果我们有- 1,flip == 1, s.d[0] == 0, bit == 1,
    所以调用者期望我们给它加2,然后翻转它。事实上,对于-1,
    这些运算是相同的。我们只进行了翻转,但由于倾斜是必需的
    (在这个意义上,倾斜必须是1或2,不为零),而翻转不是,
    我们需要改变旗帜来声明我们只是倾斜。 */
    let mut global_sign = if flip { -1 } else { 1 };
    s.cond_neg_assign(Choice::from(flip as u8));
    global_sign *= if not_neg_one { 1 } else { 0 } * 2 - 1;
    let skew = 1 << (if bit { 1 } else { 0 });

    let mut u_last: i32 = s.shr_int(w) as i32;
    let mut u: i32 = 0;
    while word * w < WNAF_BITS {
        u = s.shr_int(w) as i32;
        let even = (u & 1) == 0;
        let sign = 2 * (if u_last > 0 { 1 } else { 0 }) - 1;
        u += sign * if even { 1 } else { 0 };
        u_last -= sign * if even { 1 } else { 0 } * (1 << w);

        wnaf[word] = (u_last as i32 * global_sign as i32) as i32;
        word += 1;

        u_last = u;
    }
    wnaf[word] = u * global_sign as i32;

    debug_assert!(s.is_zero());
    let wnaf_size = (WNAF_BITS + w - 1) / w;
    debug_assert!(word == wnaf_size);

    skew
}

set_ge:将给定的仿射坐标转换为雅克比坐标

    pub fn set_ge(&mut self, a: &Affine) {
        self.infinity = a.infinity;
        self.x = a.x;
        self.y = a.y;
        self.z.set_int(1);
    }

odd_multiples_table_globalz_windowa

fn odd_multiples_table_globalz_windowa(
    pre: &mut [Affine; ECMULT_TABLE_SIZE_A],
    globalz: &mut Field,
    a: &Jacobian,
) {
    let mut prej: [Jacobian; ECMULT_TABLE_SIZE_A] = Default::default();
    let mut zr: [Field; ECMULT_TABLE_SIZE_A] = Default::default();

    odd_multiples_table(&mut prej, &mut zr, a);
    globalz_set_table_gej(pre, globalz, &prej, &zr);
}

table_get_ge_const

fn table_get_ge_const(r: &mut Affine, pre: &[Affine], n: i32, w: usize) {
    let abs_n = n * (if n > 0 { 1 } else { 0 } * 2 - 1);
    let idx_n = abs_n / 2;
    debug_assert!(n & 1 == 1);
    debug_assert!(n >= -((1 << (w - 1)) - 1));
    debug_assert!(n <= ((1 << (w - 1)) - 1));
    for m in 0..pre.len() {
        let flag = m == idx_n as usize;
        r.x.cmov(&pre[m].x, flag);
        r.y.cmov(&pre[m].y, flag);
    }
    r.infinity = false;
    let neg_y = r.y.neg(1);
    r.y.cmov(&neg_y, n != abs_n);
}

double_nonzero_in_place

//如果rzr是not- null,则r->z = a->z * *rzr(其中∞意味着隐式z = 0).
//a可以不为零。时间常数。
    pub fn double_nonzero_in_place(&mut self, a: &Jacobian, rzr: Option<&mut Field>) {
        debug_assert!(!self.is_infinity());
        self.double_var_in_place(a, rzr);
    }

add_ge

 pub fn add_ge(&self, b: &Affine) -> Jacobian {
        let mut ret = Jacobian::default();
        ret.add_ge_in_place(self, b);
        ret
    }

最后在代码段“生成共享密钥”中,介绍完生成共享密钥的函数ecmult_const及其调用的相关函数,还有一些剩余的函数。

 //生成共享密钥
        self.ecmult_const(&mut res, &pt, &s);
        pt.set_gej(&res);

        pt.x.normalize();
        pt.y.normalize();

        let x = pt.x.b32();
        let y = 0x02 | (if pt.y.is_odd() { 1 } else { 0 });

        digest.update(&[y]);
        digest.update(&x);
        Some(digest.finalize_reset())

函数set_gej实现对给定的Jacobian坐标下的点坐标,找到其在仿射坐标中的对应点:

    pub fn set_gej(&mut self, a: &Jacobian) {
        self.infinity = a.infinity;
        let mut a = *a;
        a.z = a.z.inv();
        let z2 = a.z.sqr();
        let z3 = a.z * z2;
        a.x *= z2;
        a.y *= z3;
        a.z.set_int(1);
        self.x = a.x;
        self.y = a.y;
    }

impl<D: Digest> AsRef<[u8]> for SharedSecret<D>中定义函数as_ref

impl<D: Digest> AsRef<[u8]> for SharedSecret<D> {
    fn as_ref(&self) -> &[u8] {
        &self.0.as_ref()
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值