11#include " HybridNativeUtils.hpp"
2- #include " secp256k1_wrapper .h"
2+ #include " secp256k1/include/secp256k1 .h"
33#include " hex_utils.hpp"
44#include " botan_conditional.h"
55#include < stdexcept>
6+ #include < mutex>
67
78namespace margelo ::nitro::metamask_nativeutils {
89
9- // Static global context for maximum performance
10- static secp256k1_context* g_ctx = nullptr ;
10+ // Static global context for maximum performance.
11+ // Made const and initialized with a call-once guard for thread safety.
12+ static std::once_flag g_ctx_once;
13+ static const secp256k1_context* g_ctx = nullptr ;
1114
12- // Initialize context once (thread-safe)
1315static void initializeContext () {
16+ std::call_once (g_ctx_once, []() {
17+ g_ctx = secp256k1_context_create (SECP256K1_CONTEXT_NONE);
18+ });
19+
1420 if (!g_ctx) {
15- g_ctx = secp256k1_context_create (SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY );
21+ throw std::runtime_error ( " Failed to initialize secp256k1 context " );
1622 }
1723}
1824
25+ // Helper that wraps secp256k1_ec_pubkey_serialize and enforces the expected output length.
26+ // libsecp256k1 treats the length parameter as an in/out value: on input it is the buffer
27+ // capacity, on output it is the actual number of bytes written. We defensively verify that
28+ // the actual length matches the format we requested (33 or 65 bytes) so that future changes
29+ // in libsecp256k1 cannot cause us to read uninitialized or truncated public key data.
30+ static void serializeSecp256k1PubkeyChecked (
31+ const secp256k1_pubkey* pubkey,
32+ uint8_t * output,
33+ size_t expectedLen,
34+ unsigned int flags) {
35+ size_t outputLen = expectedLen;
36+ if (!secp256k1_ec_pubkey_serialize (g_ctx, output, &outputLen, pubkey, flags)) {
37+ throw std::runtime_error (" Failed to serialize public key" );
38+ }
39+ if (outputLen != expectedLen) {
40+ throw std::runtime_error (" Unexpected public key length from secp256k1" );
41+ }
42+ }
1943
2044// Common function to generate public key from raw private key bytes
2145static std::shared_ptr<ArrayBuffer> generatePublicKeyFromBytes (const uint8_t * privateKeyBytes, bool isCompressed) {
2246 initializeContext ();
2347
2448 // Use secp256k1's built-in validation (checks if key is not 0 and < curve order)
2549 if (!secp256k1_ec_seckey_verify (g_ctx, privateKeyBytes)) {
26- throw std::runtime_error (" private key invalid 3 " );
50+ throw std::runtime_error (" Private key is invalid " );
2751 }
2852
2953 // Create public key from private key
3054 secp256k1_pubkey pubkey;
3155 if (!secp256k1_ec_pubkey_create (g_ctx, &pubkey, privateKeyBytes)) {
32- throw std::runtime_error (" private key invalid 3 " );
56+ throw std::runtime_error (" Failed to create public key from private key " );
3357 }
3458
3559 // Serialize the public key
3660 size_t keySize = isCompressed ? 33 : 65 ;
3761 auto buffer = ArrayBuffer::allocate (keySize);
3862 auto data = static_cast <uint8_t *>(buffer->data ());
39-
40- size_t outputLen = keySize;
63+
4164 unsigned int flags = isCompressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED;
42-
43- if (!secp256k1_ec_pubkey_serialize (g_ctx, data, &outputLen, &pubkey, flags)) {
44- throw std::runtime_error (" private key invalid 3" );
45- }
65+
66+ serializeSecp256k1PubkeyChecked (&pubkey, data, keySize, flags);
4667
4768 return buffer;
4869}
4970
5071std::shared_ptr<ArrayBuffer> HybridNativeUtils::toPublicKey (const std::string& privateKey, bool isCompressed) {
51- std::string hex = privateKey;
52-
5372 // Must be exactly 64 characters (32 bytes)
54- if (hex .length () != 64 ) {
55- throw std::runtime_error (" Uint8Array expected " );
73+ if (privateKey .length () != 64 ) {
74+ throw std::runtime_error (" Private key must be 64 hex characters (32 bytes) " );
5675 }
5776
58- // Convert hex to bytes with validation
59- uint8_t seckey[32 ];
60- hexToBytes (hex, seckey, 32 );
77+ uint8_t privateKeyBytes[32 ];
78+ hexToBytes (privateKey, privateKeyBytes, 32 );
6179
62- return generatePublicKeyFromBytes (seckey , isCompressed);
80+ return generatePublicKeyFromBytes (privateKeyBytes , isCompressed);
6381}
6482
6583std::shared_ptr<ArrayBuffer> HybridNativeUtils::toPublicKeyFromBytes (const std::shared_ptr<ArrayBuffer>& privateKey, bool isCompressed) {
6684 // Validate input size (must be exactly 32 bytes for secp256k1)
6785 if (privateKey->size () != 32 ) {
68- throw std::runtime_error (" Uint8Array expected " );
86+ throw std::runtime_error (" Private key must be 32 bytes " );
6987 }
7088
7189 // Get the private key bytes directly
72- const uint8_t * seckey = static_cast <const uint8_t *>(privateKey->data ());
90+ const uint8_t * privateKeyBytes = static_cast <const uint8_t *>(privateKey->data ());
7391
74- return generatePublicKeyFromBytes (seckey , isCompressed);
92+ return generatePublicKeyFromBytes (privateKeyBytes , isCompressed);
7593}
7694
7795// Common function to generate ed25519 public key from private key bytes (seed)
@@ -93,6 +111,9 @@ std::shared_ptr<ArrayBuffer> HybridNativeUtils::getPublicKeyEd25519(const std::s
93111}
94112
95113std::shared_ptr<ArrayBuffer> HybridNativeUtils::getPublicKeyEd25519FromBytes (const std::shared_ptr<ArrayBuffer>& privateKey) {
114+ if (privateKey->size () != 32 ) {
115+ throw std::runtime_error (" Private key must be 32 bytes" );
116+ }
96117 const uint8_t * seed = static_cast <const uint8_t *>(privateKey->data ());
97118
98119 return generateEd25519PublicKeyFromBytes (seed);
@@ -106,7 +127,6 @@ static std::shared_ptr<ArrayBuffer> keccak256Hash(const uint8_t* dataBytes, size
106127
107128 hasher->update (dataBytes, dataLen);
108129
109- // Convert hex string to bytes
110130 auto result = ArrayBuffer::allocate (32 );
111131 hasher->final (static_cast <uint8_t *>(result->data ()));
112132
@@ -121,10 +141,7 @@ std::shared_ptr<ArrayBuffer> HybridNativeUtils::keccak256(const std::string& dat
121141 auto dataBuffer = ArrayBuffer::allocate (dataLen);
122142 uint8_t * dataBytes = static_cast <uint8_t *>(dataBuffer->data ());
123143
124- // Convert hex string to bytes
125- for (size_t i = 0 ; i < dataLen; i++) {
126- dataBytes[i] = (hexCharToByte (data[i * 2 ]) << 4 ) | hexCharToByte (data[i * 2 + 1 ]);
127- }
144+ hexToBytes (data, dataBytes, dataLen);
128145
129146 return keccak256FromBytes (dataBuffer);
130147}
@@ -156,10 +173,11 @@ std::shared_ptr<ArrayBuffer> HybridNativeUtils::pubToAddress(const std::shared_p
156173
157174 // Serialize to uncompressed format (65 bytes)
158175 uint8_t uncompressedKey[65 ];
159- size_t outputLen = 65 ;
160- if (!secp256k1_ec_pubkey_serialize (g_ctx, uncompressedKey, &outputLen, &parsedPubkey, SECP256K1_EC_UNCOMPRESSED)) {
161- throw std::runtime_error (" Failed to serialize public key" );
162- }
176+ serializeSecp256k1PubkeyChecked (
177+ &parsedPubkey,
178+ uncompressedKey,
179+ 65 ,
180+ SECP256K1_EC_UNCOMPRESSED);
163181
164182 // Skip the 0x04 prefix byte for keccak hashing
165183 memcpy (uncompressedPubKeyBytes, uncompressedKey + 1 , 64 );
@@ -171,7 +189,7 @@ std::shared_ptr<ArrayBuffer> HybridNativeUtils::pubToAddress(const std::shared_p
171189 }
172190 }
173191
174- auto hashResult = keccak256Hash (pubKeyBytes, pubKeySize );
192+ auto hashResult = keccak256Hash (pubKeyBytes, 64 );
175193
176194 // Return the last 20 bytes (Ethereum address)
177195 auto result = ArrayBuffer::allocate (20 );
@@ -181,7 +199,6 @@ std::shared_ptr<ArrayBuffer> HybridNativeUtils::pubToAddress(const std::shared_p
181199}
182200
183201std::shared_ptr<ArrayBuffer> HybridNativeUtils::hmacSha512 (const std::shared_ptr<ArrayBuffer>& key, const std::shared_ptr<ArrayBuffer>& data) {
184- // Get key and data pointers
185202 const uint8_t * keyBytes = static_cast <const uint8_t *>(key->data ());
186203 const uint8_t * dataBytes = static_cast <const uint8_t *>(data->data ());
187204
0 commit comments