<p>I'm teaching myself ECC using the secp256k1 curve. Ultimately I'm interested in learning about Schnorr signature aggregation and MuSig protocols, but I'm starting from the ground up.</p>
<p>I've attempted to calculate the public key given a private key, but the public key that I calculate is different than the one that libsecp returns. Maybe I'm getting byte ordering wrong somewhere, or I am misunderstanding when to use modulus. Can someone point out where my logical error is?</p>
<pre class="lang-rust prettyprint-override"><code>use num_bigint::BigUint;
use rand::Fill;
use secp256k1::{Secp256k1, SecretKey};

fn main() {
    let secp = Secp256k1::new();
    let sk = private_key();
    let sk = SecretKey::from_slice(&amp;sk.to_bytes_be()).unwrap();
    let pk = sk.public_key(&amp;secp);
    println!(&quot;Public Key: {}&quot;, hex::encode(pk.serialize_uncompressed()));
}

fn private_key() -&gt; BigUint {
    // Generate a random 32-byte private key
    let mut private_key_bytes: [u8; 32] = [0; 32];
    private_key_bytes.try_fill(&amp;mut rand::thread_rng()).unwrap();

    // Convert the private key bytes to a BigUint
    let private_key = num_bigint::BigUint::from_bytes_be(&amp;private_key_bytes);

    // Curve parameters for secp256k1
    let p = BigUint::from_bytes_be(&amp;secp256k1::constants::FIELD_SIZE);
    let n = BigUint::from_bytes_be(&amp;secp256k1::constants::CURVE_ORDER);
    let g_x = BigUint::from_bytes_be(&amp;secp256k1::constants::GENERATOR_X);
    let g_y = BigUint::from_bytes_be(&amp;secp256k1::constants::GENERATOR_Y);

    // Ensure the private key is within the valid range
    let private_key = private_key % &amp;n;

    // Compute the corresponding public key using scalar multiplication
    let public_key_x = (g_x * &amp;private_key) % &amp;p;
    let public_key_y = (g_y * &amp;private_key) % &amp;p;

    // Encode the public key
    let public_key_encoding = format!(
        &quot;04{:064}{:064}&quot;,
        hex::encode(public_key_x.to_bytes_be()),
        hex::encode(public_key_y.to_bytes_be())
    );

    println!(&quot;Private key: {:?}&quot;, private_key);
    println!(&quot;Public key: {}&quot;, public_key_encoding);

    private_key
}
</code></pre>

CC BY-SA 4.0
2023-08-21T13:18:50.457Z
The multiplication between the generator G and the private key which you use to obtain the public key is not normal (modular) multiplication, but https://en.m.wikipedia.org/wiki/Elliptic_curve_point_multiplication. It's a far more complex algorithm than what you're doing here (yours would be trivial to revert too).
Pieter Wuille
CC BY-SA 4.0
2023-08-21T13:40:46.410Z
Thanks. I will attempt to implement the double-and-add algorithm described there.
ursuscamp
CC BY-SA 4.0
2023-08-21T13:46:20.893Z
You'll also need point addition and point doubling, which are a lot more involved than the double-and-add done on top. Good luck!
Pieter Wuille
<public-key><secp256k1>
Why is my manually calculated public key different than libsecp?
ursuscamp
# Why is my manually calculated public key different than libsecp?

(Asked by: username_0 on 2023-08-21)

I'm teaching myself ECC using the secp256k1 curve. Ultimately I'm interested in learning about Schnorr signature aggregation and MuSig protocols, but I'm starting from the ground up.

I've attempted to calculate the public key given a private key, but the public key that I calculate is different than the one that libsecp returns. Maybe I'm getting byte ordering wrong somewhere, or I am misunderstanding when to use modulus. Can someone point out where my logical error is?

```rust
use num_bigint::BigUint;
use rand::Fill;
use secp256k1::{Secp256k1, SecretKey};

fn main() {
    let secp = Secp256k1::new();
    let sk = private_key();
    let sk = SecretKey::from_slice(&sk.to_bytes_be()).unwrap();
    let pk = sk.public_key(&secp);
    println!("Public Key: {}", hex::encode(pk.serialize_uncompressed()));
}

fn private_key() -> BigUint {
    // Generate a random 32-byte private key
    let mut private_key_bytes: [u8; 32] = [0; 32];
    private_key_bytes.try_fill(&mut rand::thread_rng()).unwrap();

    // Convert the private key bytes to a BigUint
    let private_key = num_bigint::BigUint::from_bytes_be(&private_key_bytes);

    // Curve parameters for secp256k1
    let p = BigUint::from_bytes_be(&secp256k1::constants::FIELD_SIZE);
    let n = BigUint::from_bytes_be(&secp256k1::constants::CURVE_ORDER);
    let g_x = BigUint::from_bytes_be(&secp256k1::constants::GENERATOR_X);
    let g_y = BigUint::from_bytes_be(&secp256k1::constants::GENERATOR_Y);

    // Ensure the private key is within the valid range
    let private_key = private_key % &n;

    // Compute the corresponding public key using scalar multiplication
    let public_key_x = (g_x * &private_key) % &p;
    let public_key_y = (g_y * &private_key) % &p;

    // Encode the public key
    let public_key_encoding = format!(
        "04{:064}{:064}",
        hex::encode(public_key_x.to_bytes_be()),
        hex::encode(public_key_y.to_bytes_be())
    );

    println!("Private key: {:?}", private_key);
    println!("Public key: {}", public_key_encoding);

    private_key
}

```

**Tags:** public-key, secp256k1

> **Comments:**
> **username_1**: The multiplication between the generator G and the private key which you use to obtain the public key is not normal (modular) multiplication, but https://en.m.wikipedia.org/wiki/Elliptic_curve_point_multiplication. It's a far more complex algorithm than what you're doing here (yours would be trivial to revert too).  
> **username_0**: Thanks. I will attempt to implement the double-and-add algorithm described there.  
> **username_1**: You'll also need point addition and point doubling, which are a lot more involved than the double-and-add done on top. Good luck!  

---

## 0 answers

