2021SC@SDUSC
本部分内容摘自维基百科,略有改动。
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)
在impl
中定义两个函数new_with_context
和new
,其中函数new
中也是直接调用函数new_with_context
。在new_with_context
中共享密钥的实现过程通过context.ecdh_raw::
来实现。
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_const
;set_ge
;odd_multiples_table_globalz_windowa
;
table_get_ge_const
;double_nonzero_in_place
;add_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
中定义函数as_ref
。
impl<D: Digest> AsRef<[u8]> for SharedSecret<D> {
fn as_ref(&self) -> &[u8] {
&self.0.as_ref()
}
}