2
$\begingroup$

If I have two secp256k1 private keys and add them together, can I derive the public key for the sum, if I only know the public keys for the two original private keys?

(I think this may be the core of understanding bip32.)

$\endgroup$
7
$\begingroup$

If I have two secp256k1 private keys and add them together, can I derive the public key for the sum, if I only know the public keys for the two original private keys?

Yes.

While the idea of 'public keys' and 'private keys' depends on the cryptosystem (and not the elliptic curve it uses), most ECC-based cryptosystems (including the one used in bitcoin) have a private key that is a secret integer $a$, and a public key that is the elliptic curve point $[a]G$, where $G$ is a publicly known elliptic curve point (which is part of the definition of the curve), and $[a]G$ is computed using 'point multiplication'.

So, your question boils down to "if we know two public keys $[a]G$ and $[b]G$ (with private keys $a$ and $b$), can I derive the public key $[a+b]G$, which is the public key for the sum of the private keys?"

The answer is, yes, because this identity always holds:

$$[a+b]G = [a]G + [b]G$$

where the addition on the left side is integer addition, and the addition on the right side is point addition. The right side is easy to compute, when we can get the public key for the sum of the private keys.

$\endgroup$
1
  • $\begingroup$ Thanks, that's a great answer - I've being trying to understand bip32 for weeks and now you've provided what I was missing. $\endgroup$
    – fadedbee
    Sep 3 '20 at 18:54
0
$\begingroup$

Here's a very rough implementation in Rust:

use rand::Rng;
use secp256k1::{Secp256k1, SecretKey, PublicKey};

fn main() {
    let secp = Secp256k1::new();

    let seed_a = rand::thread_rng().gen::<[u8; 32]>();
    let mut skey_a = SecretKey::from_slice(&seed_a).expect("32 bytes, within curve order");
    let pkey_a = PublicKey::from_secret_key(&secp, &skey_a);

    println!("skey_a {:?}", skey_a);
    println!("pkey_a {:?}", pkey_a);

    let seed_b = rand::thread_rng().gen::<[u8; 32]>();
    let skey_b = SecretKey::from_slice(&seed_b).expect("32 bytes, within curve order");
    let pkey_b = PublicKey::from_secret_key(&secp, &skey_b);

    println!("skey_b {:?}", skey_b);
    println!("pkey_b {:?}", pkey_b);

    skey_a.add_assign(&seed_b).unwrap(); // there is no plain add, nor an obvious way to get the bytes out of an skey
    let skey_sum = skey_a;
    let pkey_sum_from_skeys = PublicKey::from_secret_key(&secp, &skey_sum);

    println!("skey_sum {:?}", skey_sum);
    println!("pkey_sum_from_skeys {:?}", pkey_sum_from_skeys);

    // calculate the sum of public keys without needing access to secret keys
    let pkey_sum_from_pkeys = pkey_a.combine(&pkey_b).unwrap();

    println!("pkey_sum_from_pkeys {:?}", pkey_sum_from_pkeys);
}

A sample output is:

skey_a SecretKey(ce9b44fedaa9aa82ee394a488df5ac55bdd3bd62c8cae45bebc1c91174fac2c2)
pkey_a PublicKey(c2d220e65b1a612405f7b18aa503132b2f29b9ac993bdcfc65ac3dfec4192856ad5da30b482ccb42c63c809703bb3cfac3644a586c8635f3178462d53e351fdf)
skey_b SecretKey(a8bfd58be7c9e14c27f0e60a00c1320e79698c8747cfa0cca0a2180267efc9ca)
pkey_b PublicKey(de8df52ed48d2c3320c03344a3fe859d61015e5f8d45b0df9aaa8d056c784e7e55a61a53630ee016e0bc8ac21d6ae4cd92e0ef91e74281d9410167b982764a8e)
skey_sum SecretKey(775b1a8ac2738bcf162a30528eb6de657c8e6d036151e4eccc9182870cb44b4b)
pkey_sum_from_skeys PublicKey(f91cf9ee526dab8a955709385c6ae5a7f757cf82278af1d670ab6b33f2f28d8716cbddf253047fa9ff6b152a6a1986213482d9ff6fdfc3883e481d7133d0045e)
pkey_sum_from_pkeys PublicKey(f91cf9ee526dab8a955709385c6ae5a7f757cf82278af1d670ab6b33f2f28d8716cbddf253047fa9ff6b152a6a1986213482d9ff6fdfc3883e481d7133d0045e)
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.