Skip to content

Commit

Permalink
Add diffieHellman method to ECPrivateKey
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewLM committed May 28, 2024
1 parent a6e1fb1 commit eadf3e0
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 0 deletions.
1 change: 1 addition & 0 deletions coinlib/bin/build_wasm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ RUN ${WASI_SDK_PATH}/bin/wasm-ld \
--export secp256k1_xonly_pubkey_parse \
--export secp256k1_schnorrsig_sign32 \
--export secp256k1_schnorrsig_verify \
--export secp256k1_ecdh \
# The secp256k1 library object files
src/libsecp256k1_la-secp256k1.o \
src/libsecp256k1_precomputed_la-precomputed_ecmult.o \
Expand Down
6 changes: 6 additions & 0 deletions coinlib/lib/src/crypto/ec_private_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ class ECPrivateKey {
return newScalar == null ? null : ECPrivateKey(newScalar, compressed: compressed);
}

/// Generates a 32-byte secret that can be used as a symmetric key shared
/// between two EC keys. The secret can be generated by using one private key
/// and the other public key.
Uint8List diffieHellman(ECPublicKey pubkey)
=> secp256k1.ecdh(_data, pubkey.data);

/// Get the private key where the public key always has an even Y-coordinate
/// for any X-coordinate. This is used for Schnorr signatures.
ECPrivateKey get xonly => pubkey.yIsEven
Expand Down
92 changes: 92 additions & 0 deletions coinlib/lib/src/secp256k1/secp256k1.ffi.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2094,6 +2094,75 @@ class NativeSecp256k1 {
ffi.Pointer<ffi.UnsignedChar>,
int,
ffi.Pointer<secp256k1_xonly_pubkey>)>();

/// An implementation of SHA256 hash function that applies to compressed public key.
/// Populates the output parameter with 32 bytes.
late final ffi.Pointer<secp256k1_ecdh_hash_function>
_secp256k1_ecdh_hash_function_sha256 =
_lookup<secp256k1_ecdh_hash_function>(
'secp256k1_ecdh_hash_function_sha256');

secp256k1_ecdh_hash_function get secp256k1_ecdh_hash_function_sha256 =>
_secp256k1_ecdh_hash_function_sha256.value;

/// A default ECDH hash function (currently equal to secp256k1_ecdh_hash_function_sha256).
/// Populates the output parameter with 32 bytes.
late final ffi.Pointer<secp256k1_ecdh_hash_function>
_secp256k1_ecdh_hash_function_default =
_lookup<secp256k1_ecdh_hash_function>(
'secp256k1_ecdh_hash_function_default');

secp256k1_ecdh_hash_function get secp256k1_ecdh_hash_function_default =>
_secp256k1_ecdh_hash_function_default.value;

/// Compute an EC Diffie-Hellman secret in constant time
///
/// Returns: 1: exponentiation was successful
/// 0: scalar was invalid (zero or overflow) or hashfp returned 0
/// Args: ctx: pointer to a context object.
/// Out: output: pointer to an array to be filled by hashfp.
/// In: pubkey: a pointer to a secp256k1_pubkey containing an initialized public key.
/// seckey: a 32-byte scalar with which to multiply the point.
/// hashfp: pointer to a hash function. If NULL,
/// secp256k1_ecdh_hash_function_sha256 is used
/// (in which case, 32 bytes will be written to output).
/// data: arbitrary data pointer that is passed through to hashfp
/// (can be NULL for secp256k1_ecdh_hash_function_sha256).
int secp256k1_ecdh(
ffi.Pointer<secp256k1_context> ctx,
ffi.Pointer<ffi.UnsignedChar> output,
ffi.Pointer<secp256k1_pubkey> pubkey,
ffi.Pointer<ffi.UnsignedChar> seckey,
secp256k1_ecdh_hash_function hashfp,
ffi.Pointer<ffi.Void> data,
) {
return _secp256k1_ecdh(
ctx,
output,
pubkey,
seckey,
hashfp,
data,
);
}

late final _secp256k1_ecdhPtr = _lookup<
ffi.NativeFunction<
ffi.Int Function(
ffi.Pointer<secp256k1_context>,
ffi.Pointer<ffi.UnsignedChar>,
ffi.Pointer<secp256k1_pubkey>,
ffi.Pointer<ffi.UnsignedChar>,
secp256k1_ecdh_hash_function,
ffi.Pointer<ffi.Void>)>>('secp256k1_ecdh');
late final _secp256k1_ecdh = _secp256k1_ecdhPtr.asFunction<
int Function(
ffi.Pointer<secp256k1_context>,
ffi.Pointer<ffi.UnsignedChar>,
ffi.Pointer<secp256k1_pubkey>,
ffi.Pointer<ffi.UnsignedChar>,
secp256k1_ecdh_hash_function,
ffi.Pointer<ffi.Void>)>();
}

final class max_align_t extends ffi.Opaque {}
Expand Down Expand Up @@ -2310,6 +2379,29 @@ final class secp256k1_schnorrsig_extraparams extends ffi.Struct {
external ffi.Pointer<ffi.Void> ndata;
}

/// A pointer to a function that hashes an EC point to obtain an ECDH secret
///
/// Returns: 1 if the point was successfully hashed.
/// 0 will cause secp256k1_ecdh to fail and return 0.
/// Other return values are not allowed, and the behaviour of
/// secp256k1_ecdh is undefined for other return values.
/// Out: output: pointer to an array to be filled by the function
/// In: x32: pointer to a 32-byte x coordinate
/// y32: pointer to a 32-byte y coordinate
/// data: arbitrary data pointer that is passed through
typedef secp256k1_ecdh_hash_function
= ffi.Pointer<ffi.NativeFunction<secp256k1_ecdh_hash_functionFunction>>;
typedef secp256k1_ecdh_hash_functionFunction = ffi.Int Function(
ffi.Pointer<ffi.UnsignedChar> output,
ffi.Pointer<ffi.UnsignedChar> x32,
ffi.Pointer<ffi.UnsignedChar> y32,
ffi.Pointer<ffi.Void> data);
typedef Dartsecp256k1_ecdh_hash_functionFunction = int Function(
ffi.Pointer<ffi.UnsignedChar> output,
ffi.Pointer<ffi.UnsignedChar> x32,
ffi.Pointer<ffi.UnsignedChar> y32,
ffi.Pointer<ffi.Void> data);

const int NULL = 0;

const int SECP256K1_FLAGS_TYPE_MASK = 255;
Expand Down
24 changes: 24 additions & 0 deletions coinlib/lib/src/secp256k1/secp256k1_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ abstract class Secp256k1Base<
late int Function(
CtxPtr, HeapArrayPtr, HeapArrayPtr, int, XPubKeyPtr,
) extSchnorrVerify;
late int Function(
CtxPtr, HeapArrayPtr, PubKeyPtr, HeapArrayPtr, NullPtr, NullPtr,
) extEcdh;

// Heap arrays

Expand Down Expand Up @@ -471,6 +474,27 @@ abstract class Secp256k1Base<

}

/// Generates a Diffie-Hellman shared 32-byte hash between a private and
/// public key where the hash can be used as a shared key.
Uint8List ecdh(Uint8List privKey, Uint8List pubkey) {
_requireLoad();

key32Array.load(privKey);
_parsePubkeyIntoPtr(pubkey);

if (
extEcdh(
ctxPtr, hashArray.ptr, pubKeyPtr, key32Array.ptr, nullPtr,
nullPtr,
) != 1
) {
throw Secp256k1Exception("Cannot generate ECDH shared key");
}

return hashArray.list.sublist(0);

}

/// Specialised sub-classes should override to set the value behind the
/// sizeTPtr
set sizeT(int size);
Expand Down
1 change: 1 addition & 0 deletions coinlib/lib/src/secp256k1/secp256k1_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Secp256k1 extends Secp256k1Base<
extXOnlyPubkeyParse = _lib.secp256k1_xonly_pubkey_parse;
extSchnorrSign32 = _lib.secp256k1_schnorrsig_sign32;
extSchnorrVerify = _lib.secp256k1_schnorrsig_verify;
extEcdh = _lib.secp256k1_ecdh;

// Set heap arrays
key32Array = HeapArrayFfi(Secp256k1Base.privkeySize);
Expand Down
1 change: 1 addition & 0 deletions coinlib/lib/src/secp256k1/secp256k1_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class Secp256k1 extends Secp256k1Base<
extSchnorrVerify
= inst.functions["secp256k1_schnorrsig_verify"]
as IntFunc5;
extEcdh = inst.functions["secp256k1_ecdh"] as IntFunc6;

// Local functions for loading purposes
final contextCreate = inst.functions["secp256k1_context_create"]
Expand Down
1 change: 1 addition & 0 deletions coinlib/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ ffigen:
entry-points:
- '../coinlib_flutter/src/secp256k1/include/secp256k1_recovery.h'
- '../coinlib_flutter/src/secp256k1/include/secp256k1_schnorrsig.h'
- '../coinlib_flutter/src/secp256k1/include/secp256k1_ecdh.h'
12 changes: 12 additions & 0 deletions coinlib/test/crypto/ec_private_key_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ void main() {
}
});

test(".diffieHellman()", () {
final k1 = keyPairVectors.first.privateObj;
final k2 = keyPairVectors.last.privateObj;
final s1 = k1.diffieHellman(k2.pubkey);
final s2 = k2.diffieHellman(k1.pubkey);
final other = k1.diffieHellman(k1.pubkey);
// Secrets match
expect(bytesToHex(s1), bytesToHex(s2));
// Not the same as secret generated not by same key pairs
expect(bytesToHex(s1), isNot(bytesToHex(other)));
});

test(".xonly", () {

// Already even-y = 1
Expand Down

0 comments on commit eadf3e0

Please sign in to comment.