0

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.

  • How do I turn my 32-byte private key Buffer into an ECDH object on which I can call the method?
  • Are a Buffer of 33 bytes an acceptable value for the otherPublicKey argument?
fadedbee
  • 42,671
  • 44
  • 178
  • 308
  • 1
    Q1: a raw secp256k1 private key (32 bytes) is imported with `setPrivateKey()` (as already shown in your code snippet). Q2: a raw secp256k1 public key in compressed (33 bytes) or uncompressed format (65 bytes) is imported with `setPublicKey()` (33 bytes looks like compressed format; necessary criterion: the leading byte is 0x02 or 0x03). – Topaco Jul 07 '23 at 17:02
  • @Topaco Thanks, I was confused about what an ECDH object was. It looks like it can contain either a private key (and the derived public key), or just a public key. Have I understood correctly? – fadedbee Jul 07 '23 at 17:20
  • Right. If you import a public key, only the public key is present. If you import a private key, both are present. – Topaco Jul 07 '23 at 17:25
  • I've just got that working in my code now. If you turn your comment into an answer, I'll be happy to accept it. – fadedbee Jul 07 '23 at 17:30
  • You're welcome. I have posted my comment as an answer. – Topaco Jul 07 '23 at 18:10

2 Answers2

1

How do I turn my 32-byte private key Buffer into an ECDH object on which I can call the method?

You don't as such. You create an ECDH object and set the privatekey in it, just like the code you already posted, except that when calling setPrivateKey instead of passing a Buffer returned from Hash.digest (and containing a value produced by hashing something), pass your Buffer containing the value your want

Are a Buffer of 33 bytes an acceptable value for the otherPublicKey argument?

Maybe. The valid values for computeSecret (and setPublicKey) are representations of points on the selected curve. There are two point representations originally defined by X9.62, X9.63, and SECG SEC1, republished (more conveniently) in RFC3279 and RFC5480, and used by nearly everything today including OpenSSL which underlies nodejs' builtin crypto.

  • uncompressed: consists of one octet (byte) with value 0x04, followed by representations of the X and Y coordinates, each as big-endian unsigned the same size as the curve group order

  • compressed: consists of one octet with value 0x02 or 0x03 depending on the parity of the Y coordinate, followed by representation of (only) the X coordinate in the same format as above

Since secp256k1 is a curve with a 256-bit order, this means the point representations for it are either 65 bytes uncompressed or 33 bytes compressed, and (turned around) a 33-byte Buffer is a valid point representation if it: (1) begins with 0x02 or 0x03 (2) followed by a 32-byte big-endian unsigned value which is a valid X coordinate of a pair of curve points, or equivalently the curve equation x^3 + 7 in the finite field Fp with p as defined for secp256k1 has square roots (or in mathematical terminology, is a 'quadratic residue').

dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
1

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
Topaco
  • 40,594
  • 4
  • 35
  • 62