Search code examples
rustsecp256k1

Why are secp256k1 uncompressed public keys not formatted as expected?


A small bit of Rust code:

let secret_key = SecretKey::from_slice(&rand::thread_rng().gen::<[u8; 32]>()).expect("32 bytes, within curve order");
let public_key = PublicKey::from_secret_key(&secp, &secret_key);
let pk : [u8; 33] = public_key.serialize();
println!("secret_key {:?}", secret_key);
println!("public_key {:?}", public_key);
println!("pk {:x?}", &pk[0..32]);

produces output of the form:

secret_key SecretKey(03697e6c2168bd5f99f4df7086adf0598f9ae97cced61b072dded9f77a1e837f)
public_key PublicKey(b53c8b4a40a9512037199f4047376905cbe8008be1612e166cb01fa35a60b0c9cd17d5ef86f9c92590fe1125f0fa9b478ca5966eb4be1454b0171adaebe35cfd)
pk [3, c9, b0, 60, 5a, a3, 1f, b0, 6c, 16, 2e, 61, e1, 8b, 0, e8, cb, 5, 69, 37, 47, 40, 9f, 19, 37, 20, 51, a9, 40, 4a, 8b, 3c]
secret_key SecretKey(6d8b97d7bfc4240589d5b523dd5d87096dcbd5b14ea5f780912ab713f6fcfbb3)
public_key PublicKey(3484567baaa84424ceeb76456b6f4ec54efac4c4aa5a06215733aa5d5b2f0d06120a5d05f68dfb5a8b6c204b5efa37201350cfb7905014d239e5fd71e1e23775)
pk [2, 6, d, 2f, 5b, 5d, aa, 33, 57, 21, 6, 5a, aa, c4, c4, fa, 4e, c5, 4e, 6f, 6b, 45, 76, eb, ce, 24, 44, a8, aa, 7b, 56, 84]
secret_key SecretKey(60778385b0110ea8b780f27ca96fce19dfe37d8ad94d2492a0c5595261e9fa49)
public_key PublicKey(db1f88690d138e88544d6a47ed7718cf048abd969cfb79684a3dc19053661bc9c3341094dc84aef237832c993bc16df4b5cf0df27eaa4508ea6f7b67f4062ed7)
pk [3, c9, 1b, 66, 53, 90, c1, 3d, 4a, 68, 79, fb, 9c, 96, bd, 8a, 4, cf, 18, 77, ed, 47, 6a, 4d, 54, 88, 8e, 13, d, 69, 88, 1f]

The serialised public key looks as expected, 33 bytes, beginning with 0x02 or 0x03 to denote the sign of the missing Y component.

The X component is present in the uncompressed public key db1f8869... but it is offset by one byte and reversed. This leaves only 31 bytes for the Y component, which does not seem sufficient.

What is the endianess of the uncompressed and compressed public keys?

What have I not understood?


Solution

  • The “offset by one byte” issue is because you have your slice indexes wrong. The second value is one past the last index to be included, so instead of &pk[0..32] you want &pk[0..33], or since that is the entire array just &pk[..].

    The reversed bytes are because the debug output for the public key simply dumps the internal representation of the key. Internally the key is held as an array of int64s which is copied directly into a char array, so on a little endian system this will look like the bytes are reversed*. When using serialize (or the {} format specifier) the key is correctly formatted with the most significant byte first.

    *It’s actually a bit more complicated than that, calculations are done on an array of ints using 52 bits, and are then packed into a denser “storage” form that uses all 64 bits.