@qntx/nostr-native 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,7 +25,7 @@ Pod::Spec.new do |s|
25
25
  ]
26
26
 
27
27
  s.pod_target_xcconfig = {
28
- "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) ENABLE_MODULE_EXTRAKEYS=1 ENABLE_MODULE_SCHNORRSIG=1",
28
+ "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) ENABLE_MODULE_ECDH=1 ENABLE_MODULE_EXTRAKEYS=1 ENABLE_MODULE_SCHNORRSIG=1",
29
29
  }
30
30
 
31
31
  load 'nitrogen/generated/ios/NostrNative+autolinking.rb'
@@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD 20)
7
7
 
8
8
  # Vendored libsecp256k1 modules needed by cpp/nostr_core.c (its single-TU include of
9
9
  # vendor/secp256k1/src/secp256k1.c reads these defines).
10
- add_definitions(-DENABLE_MODULE_EXTRAKEYS -DENABLE_MODULE_SCHNORRSIG)
10
+ add_definitions(-DENABLE_MODULE_ECDH -DENABLE_MODULE_EXTRAKEYS -DENABLE_MODULE_SCHNORRSIG)
11
11
 
12
12
  # C glue + C++ hybrid. nostr_core.c itself includes the vendored secp256k1.c — the only
13
13
  # separately-compiled vendor sources are the two precomputed tables below.
@@ -29,6 +29,22 @@ std::shared_ptr<ArrayBuffer> HybridNostrNative::publicKey(
29
29
  return out;
30
30
  }
31
31
 
32
+ std::shared_ptr<ArrayBuffer> HybridNostrNative::ecdhX(
33
+ const std::shared_ptr<ArrayBuffer>& secretKey,
34
+ const std::shared_ptr<ArrayBuffer>& publicKeyX) {
35
+ if (secretKey->size() != 32) {
36
+ throw std::invalid_argument("secretKey must be 32 bytes");
37
+ }
38
+ if (publicKeyX->size() != 32) {
39
+ throw std::invalid_argument("publicKeyX must be 32 bytes");
40
+ }
41
+ auto out = ArrayBuffer::allocate(32);
42
+ if (!nostr_native_ecdh_x(secretKey->data(), publicKeyX->data(), out->data())) {
43
+ throw std::invalid_argument("invalid ECDH input");
44
+ }
45
+ return out;
46
+ }
47
+
32
48
  std::shared_ptr<ArrayBuffer> HybridNostrNative::sign(
33
49
  const std::shared_ptr<ArrayBuffer>& id,
34
50
  const std::shared_ptr<ArrayBuffer>& secretKey,
package/cpp/nostr_core.c CHANGED
@@ -15,6 +15,7 @@
15
15
  */
16
16
  #include "../vendor/secp256k1/src/secp256k1.c"
17
17
  #include "../vendor/secp256k1/include/secp256k1_extrakeys.h"
18
+ #include "../vendor/secp256k1/include/secp256k1_ecdh.h"
18
19
  #include "../vendor/secp256k1/include/secp256k1_schnorrsig.h"
19
20
 
20
21
  #include "nostr_core.h"
@@ -96,6 +97,34 @@ int nostr_core_public_key(const nostr_native_secp256k1_context *ctx,
96
97
  return ok ? NOSTR_CORE_OK : NOSTR_CORE_INVALID_INPUT;
97
98
  }
98
99
 
100
+ /* ECDH hash function: writes the shared point's x coordinate, nothing else. */
101
+ static int nostr_core_ecdh_xonly_hash(unsigned char *output, const unsigned char *x,
102
+ const unsigned char *y, void *data) {
103
+ (void)y;
104
+ (void)data;
105
+ memcpy(output, x, 32);
106
+ return 1;
107
+ }
108
+
109
+ int nostr_core_ecdh_x(const nostr_native_secp256k1_context *ctx, const unsigned char seckey[32],
110
+ const unsigned char pubkey_x[32], unsigned char out32[32]) {
111
+ unsigned char compressed[33];
112
+ nostr_native_secp256k1_pubkey point;
113
+ int ok;
114
+
115
+ if (ctx == NULL || seckey == NULL || pubkey_x == NULL || out32 == NULL) {
116
+ return NOSTR_CORE_INVALID_INPUT;
117
+ }
118
+
119
+ compressed[0] = 0x02;
120
+ memcpy(compressed + 1, pubkey_x, 32);
121
+ ok = nostr_native_secp256k1_ec_pubkey_parse(ctx, &point, compressed, 33) &&
122
+ nostr_native_secp256k1_ecdh(ctx, out32, &point, seckey, nostr_core_ecdh_xonly_hash, NULL);
123
+ nostr_native_secp256k1_memclear_explicit(compressed, sizeof(compressed));
124
+ nostr_native_secp256k1_memclear_explicit(&point, sizeof(point));
125
+ return ok ? NOSTR_CORE_OK : NOSTR_CORE_INVALID_INPUT;
126
+ }
127
+
99
128
  #ifdef NOSTR_NATIVE_TEST
100
129
 
101
130
  nostr_native_secp256k1_context *nostr_native_test_context(void) {
package/cpp/nostr_core.h CHANGED
@@ -42,6 +42,13 @@ int nostr_core_sign(const nostr_native_secp256k1_context *ctx, const unsigned ch
42
42
  int nostr_core_public_key(const nostr_native_secp256k1_context *ctx,
43
43
  const unsigned char seckey[32], unsigned char out_xonly[32]);
44
44
 
45
+ /* Computes the ECDH shared point's x coordinate: `secp256k1_ecdh` with a hash function that
46
+ * copies the x coordinate only — the shared secret both NIP-04 (AES key) and NIP-44 (HKDF
47
+ * input) derive from. `pubkey_x` is lifted to an even-Y point as `0x02‖x`. Intermediates are
48
+ * wiped on every path. out32 gets the 32-byte x on NOSTR_CORE_OK. */
49
+ int nostr_core_ecdh_x(const nostr_native_secp256k1_context *ctx, const unsigned char seckey[32],
50
+ const unsigned char pubkey_x[32], unsigned char out32[32]);
51
+
45
52
  /* Secure wipe for platform layers that must clear caller-provided regions. */
46
53
  void nostr_core_wipe(void *ptr, size_t len);
47
54
 
@@ -36,6 +36,10 @@ static nostr_native_secp256k1_context *module_context(void) {
36
36
  return module_ctx;
37
37
  }
38
38
 
39
+ int nostr_native_abi_version(void) {
40
+ return NOSTR_NATIVE_ABI_VERSION;
41
+ }
42
+
39
43
  int nostr_native_verify_serialized(const unsigned char *serialized, size_t len,
40
44
  const unsigned char id[32],
41
45
  const unsigned char pubkey[32],
@@ -48,6 +52,11 @@ int nostr_native_public_key(const unsigned char seckey[32], unsigned char out_xo
48
52
  return nostr_core_public_key(module_context(), seckey, out_xonly) == NOSTR_CORE_OK;
49
53
  }
50
54
 
55
+ int nostr_native_ecdh_x(const unsigned char seckey[32], const unsigned char pubkey_x[32],
56
+ unsigned char out32[32]) {
57
+ return nostr_core_ecdh_x(module_context(), seckey, pubkey_x, out32) == NOSTR_CORE_OK;
58
+ }
59
+
51
60
  int nostr_native_sign(const unsigned char id[32], const unsigned char seckey[32],
52
61
  const unsigned char aux_rand[32], unsigned char out_sig[64]) {
53
62
  return nostr_core_sign(module_context(), id, seckey, aux_rand, out_sig) == NOSTR_CORE_OK;
@@ -7,6 +7,14 @@
7
7
  extern "C" {
8
8
  #endif
9
9
 
10
+ /* Stable public C interface version. Bump when the export surface changes; embedders can check
11
+ * nostr_native_abi_version() at load time. v1: verify_serialized/public_key/sign.
12
+ * v2: adds ecdh_x. */
13
+ #define NOSTR_NATIVE_ABI_VERSION 2
14
+
15
+ /* Returns NOSTR_NATIVE_ABI_VERSION. */
16
+ int nostr_native_abi_version(void);
17
+
10
18
  /**
11
19
  * Return 1 iff `sha256(serialized[0..len])` equals `id` and `sig` is a valid BIP-340 signature of
12
20
  * `id` for the x-only `pubkey`. `id`/`pubkey` are 32 bytes and `sig` is 64 bytes; NULL pointers
@@ -30,6 +38,15 @@ int nostr_native_public_key(const unsigned char seckey[32], unsigned char out_xo
30
38
  int nostr_native_sign(const unsigned char id[32], const unsigned char seckey[32],
31
39
  const unsigned char aux_rand[32], unsigned char out_sig[64]);
32
40
 
41
+ /**
42
+ * Write the 32-byte x coordinate of the ECDH shared point between `seckey` and `pubkey_x`
43
+ * (lifted even-Y) to `out32` — the shared secret NIP-04 and NIP-44 derive from. The caller must
44
+ * wipe `out32` and its own `seckey` copy when done. Returns 1 on success; 0 on NULL input, an
45
+ * invalid secret key, or a `pubkey_x` that does not lift to a curve point.
46
+ */
47
+ int nostr_native_ecdh_x(const unsigned char seckey[32], const unsigned char pubkey_x[32],
48
+ unsigned char out32[32]);
49
+
33
50
  #ifdef NOSTR_NATIVE_TEST
34
51
  /* Test-only entry points (host suite only): expose the SHA-256 and Schnorr halves individually
35
52
  * so BIP-340 vectors with arbitrary-length messages and raw SHA-256 vectors can run. */
package/dist/index.d.mts CHANGED
@@ -1,15 +1,17 @@
1
- import { SerializedEventVerifier, SigningBackend } from "@qntx/nostr/core";
1
+ import { NostrBackend } from "@qntx/nostr/core";
2
2
  //#region src/index.d.ts
3
3
  /**
4
- * `SerializedEventVerifier` backed by native libsecp256k1 — plug into `createEventVerifier` from
5
- * `@qntx/nostr/core`. The Nitro hybrid object is created once at module load.
4
+ * `NostrBackend["verifySerialized"]` backed by native libsecp256k1 — plug into
5
+ * `createEventVerifier` from `@qntx/nostr/core`. The Nitro hybrid object is created once at module
6
+ * load.
6
7
  */
7
- export declare const verifySerialized: SerializedEventVerifier;
8
+ export declare const verifySerialized: NostrBackend["verifySerialized"];
8
9
  /**
9
- * `SigningBackend` backed by native libsecp256k1 — plug into `Keys.fromSecretKey(secretKey,
10
- * signing)` or `Keys.generate(signing)` so signing runs on the same randomized context as
11
- * verification. Throws on an invalid secret key or wrong-length input.
10
+ * `NostrBackend` (without `ecdhX`, which lands in T4 with the native `ecdh_x` export) backed by
11
+ * native libsecp256k1 — plug into `Keys.fromSecretKey(secretKey, signing)` or
12
+ * `Keys.generate(signing)` so signing runs on the same randomized context as verification. Throws
13
+ * on an invalid secret key or wrong-length input.
12
14
  */
13
- export declare const signing: SigningBackend;
15
+ export declare const signing: NostrBackend;
14
16
  //#endregion
15
17
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -7,16 +7,20 @@ function asArrayBuffer(view) {
7
7
  return new Uint8Array(view).buffer;
8
8
  }
9
9
  /**
10
- * `SerializedEventVerifier` backed by native libsecp256k1 — plug into `createEventVerifier` from
11
- * `@qntx/nostr/core`. The Nitro hybrid object is created once at module load.
10
+ * `NostrBackend["verifySerialized"]` backed by native libsecp256k1 — plug into
11
+ * `createEventVerifier` from `@qntx/nostr/core`. The Nitro hybrid object is created once at module
12
+ * load.
12
13
  */
13
14
  const verifySerialized = (serializedUtf8, id, pubkey, sig) => nostrNative.verifySerialized(asArrayBuffer(serializedUtf8), asArrayBuffer(id), asArrayBuffer(pubkey), asArrayBuffer(sig));
14
15
  /**
15
- * `SigningBackend` backed by native libsecp256k1 — plug into `Keys.fromSecretKey(secretKey,
16
- * signing)` or `Keys.generate(signing)` so signing runs on the same randomized context as
17
- * verification. Throws on an invalid secret key or wrong-length input.
16
+ * `NostrBackend` (without `ecdhX`, which lands in T4 with the native `ecdh_x` export) backed by
17
+ * native libsecp256k1 — plug into `Keys.fromSecretKey(secretKey, signing)` or
18
+ * `Keys.generate(signing)` so signing runs on the same randomized context as verification. Throws
19
+ * on an invalid secret key or wrong-length input.
18
20
  */
19
21
  const signing = {
22
+ ecdhX: (secretKey, publicKeyX) => new Uint8Array(nostrNative.ecdhX(asArrayBuffer(secretKey), asArrayBuffer(publicKeyX))),
23
+ verifySerialized,
20
24
  publicKey: (secretKey) => new Uint8Array(nostrNative.publicKey(asArrayBuffer(secretKey))),
21
25
  sign: (id, secretKey, auxRand) => new Uint8Array(nostrNative.sign(asArrayBuffer(id), asArrayBuffer(secretKey), asArrayBuffer(auxRand)))
22
26
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { NitroModules } from \"react-native-nitro-modules\";\n\nimport type { SerializedEventVerifier, SigningBackend } from \"@qntx/nostr/core\";\n\nimport type { NostrNative } from \"./specs/nostr-native.nitro.ts\";\n\nconst nostrNative = NitroModules.createHybridObject<NostrNative>(\"NostrNative\");\n\nfunction asArrayBuffer(view: Uint8Array): ArrayBuffer {\n const { buffer, byteOffset, byteLength } = view;\n if (buffer instanceof ArrayBuffer && byteOffset === 0 && byteLength === buffer.byteLength) {\n return buffer;\n }\n return new Uint8Array(view).buffer;\n}\n\n/**\n * `SerializedEventVerifier` backed by native libsecp256k1 — plug into `createEventVerifier` from\n * `@qntx/nostr/core`. The Nitro hybrid object is created once at module load.\n */\nexport const verifySerialized: SerializedEventVerifier = (serializedUtf8, id, pubkey, sig) =>\n nostrNative.verifySerialized(\n asArrayBuffer(serializedUtf8),\n asArrayBuffer(id),\n asArrayBuffer(pubkey),\n asArrayBuffer(sig),\n );\n\n/**\n * `SigningBackend` backed by native libsecp256k1 — plug into `Keys.fromSecretKey(secretKey,\n * signing)` or `Keys.generate(signing)` so signing runs on the same randomized context as\n * verification. Throws on an invalid secret key or wrong-length input.\n */\nexport const signing: SigningBackend = {\n publicKey: (secretKey) => new Uint8Array(nostrNative.publicKey(asArrayBuffer(secretKey))),\n sign: (id, secretKey, auxRand) =>\n new Uint8Array(\n nostrNative.sign(asArrayBuffer(id), asArrayBuffer(secretKey), asArrayBuffer(auxRand)),\n ),\n};\n"],"mappings":";;AAMA,MAAM,cAAc,aAAa,mBAAgC,aAAa;AAE9E,SAAS,cAAc,MAA+B;CACpD,MAAM,EAAE,QAAQ,YAAY,eAAe;CAC3C,IAAI,kBAAkB,eAAe,eAAe,KAAK,eAAe,OAAO,YAC7E,OAAO;CAET,OAAO,IAAI,WAAW,IAAI,CAAC,CAAC;AAC9B;;;;;AAMA,MAAa,oBAA6C,gBAAgB,IAAI,QAAQ,QACpF,YAAY,iBACV,cAAc,cAAc,GAC5B,cAAc,EAAE,GAChB,cAAc,MAAM,GACpB,cAAc,GAAG,CACnB;;;;;;AAOF,MAAa,UAA0B;CACrC,YAAY,cAAc,IAAI,WAAW,YAAY,UAAU,cAAc,SAAS,CAAC,CAAC;CACxF,OAAO,IAAI,WAAW,YACpB,IAAI,WACF,YAAY,KAAK,cAAc,EAAE,GAAG,cAAc,SAAS,GAAG,cAAc,OAAO,CAAC,CACtF;AACJ"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { NitroModules } from \"react-native-nitro-modules\";\n\nimport type { NostrBackend } from \"@qntx/nostr/core\";\n\nimport type { NostrNative } from \"./specs/nostr-native.nitro.ts\";\n\nconst nostrNative = NitroModules.createHybridObject<NostrNative>(\"NostrNative\");\n\nfunction asArrayBuffer(view: Uint8Array): ArrayBuffer {\n const { buffer, byteOffset, byteLength } = view;\n if (buffer instanceof ArrayBuffer && byteOffset === 0 && byteLength === buffer.byteLength) {\n return buffer;\n }\n return new Uint8Array(view).buffer;\n}\n\n/**\n * `NostrBackend[\"verifySerialized\"]` backed by native libsecp256k1 — plug into\n * `createEventVerifier` from `@qntx/nostr/core`. The Nitro hybrid object is created once at module\n * load.\n */\nexport const verifySerialized: NostrBackend[\"verifySerialized\"] = (\n serializedUtf8,\n id,\n pubkey,\n sig,\n) =>\n nostrNative.verifySerialized(\n asArrayBuffer(serializedUtf8),\n asArrayBuffer(id),\n asArrayBuffer(pubkey),\n asArrayBuffer(sig),\n );\n\n/**\n * `NostrBackend` (without `ecdhX`, which lands in T4 with the native `ecdh_x` export) backed by\n * native libsecp256k1 — plug into `Keys.fromSecretKey(secretKey, signing)` or\n * `Keys.generate(signing)` so signing runs on the same randomized context as verification. Throws\n * on an invalid secret key or wrong-length input.\n */\nexport const signing: NostrBackend = {\n ecdhX: (secretKey, publicKeyX) =>\n new Uint8Array(nostrNative.ecdhX(asArrayBuffer(secretKey), asArrayBuffer(publicKeyX))),\n verifySerialized,\n publicKey: (secretKey) => new Uint8Array(nostrNative.publicKey(asArrayBuffer(secretKey))),\n sign: (id, secretKey, auxRand) =>\n new Uint8Array(\n nostrNative.sign(asArrayBuffer(id), asArrayBuffer(secretKey), asArrayBuffer(auxRand)),\n ),\n};\n"],"mappings":";;AAMA,MAAM,cAAc,aAAa,mBAAgC,aAAa;AAE9E,SAAS,cAAc,MAA+B;CACpD,MAAM,EAAE,QAAQ,YAAY,eAAe;CAC3C,IAAI,kBAAkB,eAAe,eAAe,KAAK,eAAe,OAAO,YAC7E,OAAO;CAET,OAAO,IAAI,WAAW,IAAI,CAAC,CAAC;AAC9B;;;;;;AAOA,MAAa,oBACX,gBACA,IACA,QACA,QAEA,YAAY,iBACV,cAAc,cAAc,GAC5B,cAAc,EAAE,GAChB,cAAc,MAAM,GACpB,cAAc,GAAG,CACnB;;;;;;;AAQF,MAAa,UAAwB;CACnC,QAAQ,WAAW,eACjB,IAAI,WAAW,YAAY,MAAM,cAAc,SAAS,GAAG,cAAc,UAAU,CAAC,CAAC;CACvF;CACA,YAAY,cAAc,IAAI,WAAW,YAAY,UAAU,cAAc,SAAS,CAAC,CAAC;CACxF,OAAO,IAAI,WAAW,YACpB,IAAI,WACF,YAAY,KAAK,cAAc,EAAE,GAAG,cAAc,SAAS,GAAG,cAAc,OAAO,CAAC,CACtF;AACJ"}
@@ -17,6 +17,7 @@ namespace margelo::nitro::nostrnative {
17
17
  prototype.registerHybridMethod("verifySerialized", &HybridNostrNativeSpec::verifySerialized);
18
18
  prototype.registerHybridMethod("publicKey", &HybridNostrNativeSpec::publicKey);
19
19
  prototype.registerHybridMethod("sign", &HybridNostrNativeSpec::sign);
20
+ prototype.registerHybridMethod("ecdhX", &HybridNostrNativeSpec::ecdhX);
20
21
  });
21
22
  }
22
23
 
@@ -51,6 +51,7 @@ namespace margelo::nitro::nostrnative {
51
51
  virtual bool verifySerialized(const std::shared_ptr<ArrayBuffer>& serializedUtf8, const std::shared_ptr<ArrayBuffer>& id, const std::shared_ptr<ArrayBuffer>& pubkey, const std::shared_ptr<ArrayBuffer>& sig) = 0;
52
52
  virtual std::shared_ptr<ArrayBuffer> publicKey(const std::shared_ptr<ArrayBuffer>& secretKey) = 0;
53
53
  virtual std::shared_ptr<ArrayBuffer> sign(const std::shared_ptr<ArrayBuffer>& id, const std::shared_ptr<ArrayBuffer>& secretKey, const std::shared_ptr<ArrayBuffer>& auxRand) = 0;
54
+ virtual std::shared_ptr<ArrayBuffer> ecdhX(const std::shared_ptr<ArrayBuffer>& secretKey, const std::shared_ptr<ArrayBuffer>& publicKeyX) = 0;
54
55
 
55
56
  protected:
56
57
  // Hybrid Setup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qntx/nostr-native",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Native libsecp256k1 (Nitro) verification and signing backend for @qntx/nostr on React Native",
5
5
  "keywords": [
6
6
  "bip340",
@@ -61,17 +61,17 @@
61
61
  "@arethetypeswrong/cli": "0.18.5",
62
62
  "@noble/curves": "2.4.0",
63
63
  "@noble/hashes": "2.4.0",
64
- "@qntx/nostr": "0.13.0",
64
+ "@qntx/nostr": "0.15.0",
65
65
  "@qntx/tsconfig": "1.0.1",
66
- "@types/node": "26.6.4",
66
+ "@types/node": "26.6.5",
67
67
  "nitrogen": "0.37.1",
68
68
  "publint": "0.3.25",
69
69
  "react-native-nitro-modules": "0.37.1",
70
70
  "typescript": "7.0.2",
71
- "vite-plus": "1.0.0"
71
+ "vite-plus": "1.2.0"
72
72
  },
73
73
  "peerDependencies": {
74
- "@qntx/nostr": "^0.13.0",
74
+ "@qntx/nostr": "^0.15.0",
75
75
  "react-native": "*",
76
76
  "react-native-nitro-modules": "^0.37.1"
77
77
  },
@@ -0,0 +1,79 @@
1
+ /***********************************************************************
2
+ * Copyright (c) 2015 Andrew Poelstra *
3
+ * Distributed under the MIT software license, see the accompanying *
4
+ * file COPYING or https://www.opensource.org/licenses/mit-license.php.*
5
+ ***********************************************************************/
6
+
7
+ #ifndef SECP256K1_MODULE_ECDH_MAIN_H
8
+ #define SECP256K1_MODULE_ECDH_MAIN_H
9
+
10
+ #include "../../../include/secp256k1_ecdh.h"
11
+ #include "../../ecmult_const_impl.h"
12
+
13
+ static int ecdh_hash_function_sha256_impl(const nostr_native_secp256k1_hash_ctx *hash_ctx, unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) {
14
+ unsigned char version = (y32[31] & 0x01) | 0x02;
15
+ nostr_native_secp256k1_sha256 sha;
16
+ (void)data;
17
+
18
+ nostr_native_secp256k1_sha256_initialize(&sha);
19
+ nostr_native_secp256k1_sha256_write(hash_ctx, &sha, &version, 1);
20
+ nostr_native_secp256k1_sha256_write(hash_ctx, &sha, x32, 32);
21
+ nostr_native_secp256k1_sha256_finalize(hash_ctx, &sha, output);
22
+ nostr_native_secp256k1_sha256_clear(&sha);
23
+
24
+ return 1;
25
+ }
26
+
27
+ static int ecdh_hash_function_sha256(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) {
28
+ return ecdh_hash_function_sha256_impl(nostr_native_secp256k1_get_hash_context(nostr_native_secp256k1_context_static), output, x32, y32, data);
29
+ }
30
+
31
+ const nostr_native_secp256k1_ecdh_hash_function nostr_native_secp256k1_ecdh_hash_function_sha256 = ecdh_hash_function_sha256;
32
+ const nostr_native_secp256k1_ecdh_hash_function nostr_native_secp256k1_ecdh_hash_function_default = ecdh_hash_function_sha256;
33
+
34
+ int nostr_native_secp256k1_ecdh(const nostr_native_secp256k1_context* ctx, unsigned char *output, const nostr_native_secp256k1_pubkey *point, const unsigned char *scalar, nostr_native_secp256k1_ecdh_hash_function hashfp, void *data) {
35
+ int ret = 0;
36
+ int overflow = 0;
37
+ nostr_native_secp256k1_gej res;
38
+ nostr_native_secp256k1_ge pt;
39
+ nostr_native_secp256k1_scalar s;
40
+ unsigned char x[32];
41
+ unsigned char y[32];
42
+
43
+ VERIFY_CHECK(ctx != NULL);
44
+ ARG_CHECK(output != NULL);
45
+ ARG_CHECK(point != NULL);
46
+ ARG_CHECK(scalar != NULL);
47
+
48
+ nostr_native_secp256k1_pubkey_load(ctx, &pt, point);
49
+ nostr_native_secp256k1_scalar_set_b32(&s, scalar, &overflow);
50
+
51
+ overflow |= nostr_native_secp256k1_scalar_is_zero(&s);
52
+ nostr_native_secp256k1_scalar_cmov(&s, &nostr_native_secp256k1_scalar_one, overflow);
53
+
54
+ nostr_native_secp256k1_ecmult_const(&res, &pt, &s);
55
+ nostr_native_secp256k1_ge_set_gej(&pt, &res);
56
+
57
+ /* Compute a hash of the point */
58
+ nostr_native_secp256k1_fe_normalize(&pt.x);
59
+ nostr_native_secp256k1_fe_normalize(&pt.y);
60
+ nostr_native_secp256k1_fe_get_b32(x, &pt.x);
61
+ nostr_native_secp256k1_fe_get_b32(y, &pt.y);
62
+
63
+ if (hashfp == NULL || hashfp == nostr_native_secp256k1_ecdh_hash_function_sha256) {
64
+ /* Use ctx-aware function by default */
65
+ ret = ecdh_hash_function_sha256_impl(nostr_native_secp256k1_get_hash_context(ctx), output, x, y, data);
66
+ } else {
67
+ ret = hashfp(output, x, y, data);
68
+ }
69
+
70
+ nostr_native_secp256k1_memclear_explicit(x, sizeof(x));
71
+ nostr_native_secp256k1_memclear_explicit(y, sizeof(y));
72
+ nostr_native_secp256k1_scalar_clear(&s);
73
+ nostr_native_secp256k1_ge_clear(&pt);
74
+ nostr_native_secp256k1_gej_clear(&res);
75
+
76
+ return !!ret & !overflow;
77
+ }
78
+
79
+ #endif /* SECP256K1_MODULE_ECDH_MAIN_H */