diff --git a/cpp/HybridNativeUtils.cpp b/cpp/HybridNativeUtils.cpp index cc4c370..86bd139 100644 --- a/cpp/HybridNativeUtils.cpp +++ b/cpp/HybridNativeUtils.cpp @@ -74,6 +74,30 @@ std::shared_ptr HybridNativeUtils::toPublicKeyFromBytes(const std:: return generatePublicKeyFromBytes(seckey, isCompressed); } +// Common function to generate ed25519 public key from private key bytes (seed) +static std::shared_ptr generateEd25519PublicKeyFromBytes(const uint8_t* privateKeyBytes) { + auto buffer = ArrayBuffer::allocate(32); + uint8_t* publicKey = static_cast(buffer->data()); + uint8_t secretKey[64]; + + Botan::ed25519_gen_keypair(publicKey, secretKey, privateKeyBytes); + + return buffer; +} + +std::shared_ptr HybridNativeUtils::getPublicKeyEd25519(const std::string& privateKey) { + uint8_t seed[32]; + hexToBytes(privateKey, seed, 32); + + return generateEd25519PublicKeyFromBytes(seed); +} + +std::shared_ptr HybridNativeUtils::getPublicKeyEd25519FromBytes(const std::shared_ptr& privateKey) { + const uint8_t* seed = static_cast(privateKey->data()); + + return generateEd25519PublicKeyFromBytes(seed); +} + static std::shared_ptr keccak256Hash(const uint8_t* dataBytes, size_t dataLen) { auto hasher = Botan::HashFunction::create("Keccak-1600(256)"); if (!hasher) { diff --git a/cpp/HybridNativeUtils.hpp b/cpp/HybridNativeUtils.hpp index 8ddcfd5..ee92eac 100644 --- a/cpp/HybridNativeUtils.hpp +++ b/cpp/HybridNativeUtils.hpp @@ -12,6 +12,8 @@ class HybridNativeUtils : public HybridNativeUtilsSpec { double multiply(double a, double b) override; std::shared_ptr toPublicKey(const std::string& privateKey, bool isCompressed) override; std::shared_ptr toPublicKeyFromBytes(const std::shared_ptr& privateKey, bool isCompressed) override; + std::shared_ptr getPublicKeyEd25519(const std::string& privateKey) override; + std::shared_ptr getPublicKeyEd25519FromBytes(const std::shared_ptr& privateKey) override; std::shared_ptr keccak256(const std::string& data) override; std::shared_ptr keccak256FromBytes(const std::shared_ptr& data) override; std::shared_ptr pubToAddress(const std::shared_ptr& pubKey, bool sanitize = false) override; diff --git a/scripts/build-botan.sh b/scripts/build-botan.sh index 90c13ba..6312afb 100755 --- a/scripts/build-botan.sh +++ b/scripts/build-botan.sh @@ -21,7 +21,7 @@ echo "Output directory: $OUTPUT_DIR" mkdir -p "$BOTAN_GENERATED_DIR" # Configuration variables -BOTAN_MODULES="keccak,hmac,sha2_64" +BOTAN_MODULES="keccak,hmac,sha2_64,ed25519" COMMON_FLAGS="--amalgamation --minimized-build --disable-cc-tests" echo "📦 Using modules: $BOTAN_MODULES" diff --git a/src/NativeUtils.nitro.ts b/src/NativeUtils.nitro.ts index c13e224..8f254cb 100644 --- a/src/NativeUtils.nitro.ts +++ b/src/NativeUtils.nitro.ts @@ -8,6 +8,8 @@ export interface NativeUtils privateKey: ArrayBuffer, isCompressed: boolean, ): ArrayBuffer; + getPublicKeyEd25519(privateKey: string): ArrayBuffer; + getPublicKeyEd25519FromBytes(privateKey: ArrayBuffer): ArrayBuffer; keccak256(data: string): ArrayBuffer; keccak256FromBytes(data: ArrayBuffer): ArrayBuffer; pubToAddress(pubKey: ArrayBuffer, sanitize: boolean): ArrayBuffer; diff --git a/src/index.tsx b/src/index.tsx index 33228d7..2b0f2b8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -134,3 +134,42 @@ export function hmacSha512(key: Uint8Array, data: Uint8Array): Uint8Array { return arrayBufferToUint8Array(result); } + +/** + * Generate an Ed25519 public key from a private key using native implementation. + * This is a fast native implementation that matches the noble/curves ed25519 API. + * + * @param privateKey - The 32-byte Ed25519 private key as Uint8Array or hex string + * @param _compressed - Ignored parameter for API compatibility (Ed25519 keys have no compressed form) + * @returns Uint8Array containing 32-byte Ed25519 public key + */ +export function getPublicKeyEd25519( + privateKey: Uint8Array | string, + _compressed?: boolean, +): Uint8Array { + let result: ArrayBuffer; + + if (typeof privateKey === 'string') { + // 64 characters = 32 bytes + if (privateKey.length !== 64) { + throw new Error( + 'Ed25519 private key must be 32 bytes (64 hex characters)', + ); + } + + result = NativeUtilsHybridObject.getPublicKeyEd25519(privateKey); + } else if (privateKey instanceof Uint8Array) { + if (privateKey.length !== 32) { + throw new Error('Ed25519 private key must be 32 bytes'); + } + + const privateKeyBuffer = uint8ArrayToArrayBuffer(privateKey); + + result = + NativeUtilsHybridObject.getPublicKeyEd25519FromBytes(privateKeyBuffer); + } else { + throw new Error('Ed25519 private key must be a hex string or Uint8Array'); + } + + return arrayBufferToUint8Array(result); +}