Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions cpp/HybridNativeUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,30 @@ std::shared_ptr<ArrayBuffer> HybridNativeUtils::toPublicKeyFromBytes(const std::
return generatePublicKeyFromBytes(seckey, isCompressed);
}

// Common function to generate ed25519 public key from private key bytes (seed)
static std::shared_ptr<ArrayBuffer> generateEd25519PublicKeyFromBytes(const uint8_t* privateKeyBytes) {
auto buffer = ArrayBuffer::allocate(32);
uint8_t* publicKey = static_cast<uint8_t*>(buffer->data());
uint8_t secretKey[64];

Botan::ed25519_gen_keypair(publicKey, secretKey, privateKeyBytes);

return buffer;
}

std::shared_ptr<ArrayBuffer> HybridNativeUtils::getPublicKeyEd25519(const std::string& privateKey) {
uint8_t seed[32];
hexToBytes(privateKey, seed, 32);

return generateEd25519PublicKeyFromBytes(seed);
}

std::shared_ptr<ArrayBuffer> HybridNativeUtils::getPublicKeyEd25519FromBytes(const std::shared_ptr<ArrayBuffer>& privateKey) {
const uint8_t* seed = static_cast<const uint8_t*>(privateKey->data());

return generateEd25519PublicKeyFromBytes(seed);
}

static std::shared_ptr<ArrayBuffer> keccak256Hash(const uint8_t* dataBytes, size_t dataLen) {
auto hasher = Botan::HashFunction::create("Keccak-1600(256)");
if (!hasher) {
Expand Down
2 changes: 2 additions & 0 deletions cpp/HybridNativeUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class HybridNativeUtils : public HybridNativeUtilsSpec {
double multiply(double a, double b) override;
std::shared_ptr<ArrayBuffer> toPublicKey(const std::string& privateKey, bool isCompressed) override;
std::shared_ptr<ArrayBuffer> toPublicKeyFromBytes(const std::shared_ptr<ArrayBuffer>& privateKey, bool isCompressed) override;
std::shared_ptr<ArrayBuffer> getPublicKeyEd25519(const std::string& privateKey) override;
std::shared_ptr<ArrayBuffer> getPublicKeyEd25519FromBytes(const std::shared_ptr<ArrayBuffer>& privateKey) override;
std::shared_ptr<ArrayBuffer> keccak256(const std::string& data) override;
std::shared_ptr<ArrayBuffer> keccak256FromBytes(const std::shared_ptr<ArrayBuffer>& data) override;
std::shared_ptr<ArrayBuffer> pubToAddress(const std::shared_ptr<ArrayBuffer>& pubKey, bool sanitize = false) override;
Expand Down
2 changes: 1 addition & 1 deletion scripts/build-botan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions src/NativeUtils.nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
39 changes: 39 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading