node.jscryptographysecp256k1

Creating a ECDH shared secret from raw public and private keys


I've read the documentation example (below), but the keys there are being generated, rather than imported from raw bytes.

const crypto = require('crypto');
const alice = crypto.createECDH('secp256k1');
const bob = crypto.createECDH('secp256k1');

// Note: This is a shortcut way to specify one of Alice's previous private
// keys. It would be unwise to use such a predictable private key in a real
// application.
alice.setPrivateKey(
  crypto.createHash('sha256').update('alice', 'utf8').digest()
);

// Bob uses a newly generated cryptographically strong
// pseudorandom key pair bob.generateKeys();

const alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex');
const bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex');

// alice_secret and bob_secret should be the same shared secret value
console.log(alice_secret === bob_secret);

Using curve secp256k1, I want to create the ECDH shared secret from one 33-byte compressed public key and one 32-byte private key (from two different key pairs).

ecdh.computeSecret(otherPublicKey, ...) looks like the correct method.


Solution

  • Regarding your first question: A raw secp256k1 private key (32 bytes) is imported with setPrivateKey() (as already shown in your code snippet).

    Regarding your second question: A raw secp256k1 public key in compressed (33 bytes) or uncompressed format (65 bytes) is imported with setPublicKey(). The uncompressed format consists of a leading 0x04 byte followed by the x and y coordinates, the compressed format of a leading 0x02 byte (even y) or 0x03 byte (odd y) followed by the x coordinate.
    Since your public key has 33 bytes, it is probably a key in compressed format (check if the leading byte is 0x02 or 0x03).

    Regarding your question from the comment: If the private key is imported, the ECDH instance contains both keys (since the public key can be derived from the private one). If only the public key is imported, the ECDH instance contains only the public key.
    The latter is necessary because when the shared secret is generated, only the public key of the other side is known (but not the corresponding private key). This is illustrated in the following sample code for key import and shared secret generation:

    const crypto = require('crypto');
    
    // Alice's side
    var alicePrivateKeyHex = '9b1958afbafb00ebbd15571b5902dfcb728ea04052fa046c809998a3f2ed1a0e';
    var bobPublicCompressedKeyHex = '021cebfbe3ee80a92e1b96b96bced8d7a32398edc81472c46947a7777c23f019d4'; // e.g. compressed format
    var alice = crypto.createECDH('secp256k1');
    var bobOnlyPublic = crypto.createECDH('secp256k1');
    alice.setPrivateKey(alicePrivateKeyHex, 'hex');                         // import own private key
    bobOnlyPublic.setPublicKey(bobPublicCompressedKeyHex, 'hex');           // import public key of other side
    var alice_secret = alice.computeSecret(bobOnlyPublic.getPublicKey(), null, 'hex');
    
    // Bob's side
    var bobPrivateKeyHex = '7380a48330bb5e67a3a96bfa39ae69c6528080a3da7110f712f474a0b46caa62';
    var alicePublicUncompressedKeyHex = '04598c37668a0cd2b219db9ff68a3aee19a0d1f97f8986a541a694199372cac249999ad56f5ede64ed74a1d611290071a0dc456314f9a5910f5a702c31231f5eda'; // e.g. uncompressed format
    var bob = crypto.createECDH('secp256k1');
    var aliceOnlyPublic = crypto.createECDH('secp256k1');
    bob.setPrivateKey(bobPrivateKeyHex, 'hex');                             // import own private key
    aliceOnlyPublic.setPublicKey(alicePublicUncompressedKeyHex, 'hex');     // import public key of other side
    var bob_secret = bob.computeSecret(aliceOnlyPublic.getPublicKey(), null, 'hex');
    
    console.log(alice_secret === bob_secret); // true