Repository: hiero-ledger/hiero-sdk-js
Pull request #3068: feat: key algorithm identifier
Author: login: ivaylonikolov7
Created: 2025-04-24T22:09:36Z | Merged: 2025-05-23T07:05:58Z

**Description**:
- adds a library that determines the keys 
- updates the files to use the correct implementation when they have to determine the key type 

Fixes #2170 

**Notes for reviewer**:
<!-- Provide logs, performance numbers or screenshots of the new functionality -->

**Checklist**

- [x] Documented (Code comments, README, etc.)
- [x] Tested (unit, integration, etc.)


Linked issue #2170: Implement method to determine which encryption algorithm is used for key generation - `ECDSA` or `ED25519` 
Having the private/public key It would be beneficial for users to be able to determine which type of encryption algorithm (`ECDSA`,`ED25519`) was used for the key generation. 

How is this possible in cryptography:
1. Having the private key we can extract the type of algorithm that is used for generation.
2. By having the private key in DER  encoded format.
3. Then decoding it using a [ASN1 decoder](https://the-x.cn/en-US/encodings/Asn1.aspx) (just an example tool) most likely a third party library for JS project
4. Then after decoding, it will return whether the key is `secp256k1` or `curveEd25519`

For the purpose
1. Good research on what third library should be used
    1. One possible solution is `asn1js` library would be for the decoding 
    2. In Java SDK `bouncycastle` is used. A check can be done if there's alternative for JavaScript. 
        1. Example in **Java SDK**: [fromBytesDER](https://github.com/hashgraph/hedera-sdk-java/blob/546b49b8f95387666a99800e344153c3c3dddf8f/sdk/src/main/java/com/hedera/hashgraph/sdk/PrivateKey.java#L227) 
        2. Example in **C++ SDK**: [decode](https://github.com/hashgraph/hedera-sdk-cpp/blob/5030f4768d88854f277f4904c172ceb6c91370cc/src/sdk/main/include/impl/ASN1Object.h#L61) 
2. Tests with a lot of different keys (at least hundred).
    1. Same/different byte lengths
    2. Same/different types  (ECDSA, ED25519, RSA, Blowfish, Twofish, DES, TripleDES)



Diff:
diff --git a/src/PrivateKey.js b/src/PrivateKey.js
index b1b13cb3c0..622904f83f 100644
--- a/src/PrivateKey.js
+++ b/src/PrivateKey.js
@@ -1,15 +1,16 @@
 // SPDX-License-Identifier: Apache-2.0
-
+import { hexlify } from "@ethersproject/bytes";
 import { PrivateKey as PrivateKeyCrypto } from "@hashgraph/cryptography";
+import { proto } from "@hashgraph/proto";
 import Mnemonic from "./Mnemonic.js";
 import PublicKey from "./PublicKey.js";
 import Key from "./Key.js";
 import CACHE from "./Cache.js";
 import SignatureMap from "./transaction/SignatureMap.js";
-
 import AccountId from "./account/AccountId.js";
 import TransactionId from "./transaction/TransactionId.js";
-import { proto } from "@hashgraph/proto";
+import { decode } from "./encoding/hex.js";
+import { ASN1Decoder } from "./util/ASN1-Decoder.js";
 
 /**
  * @typedef {import("./transaction/Transaction.js").default} Transaction
@@ -99,6 +100,18 @@ export default class PrivateKey extends Key {
      * @returns {PrivateKey}
      */
     static fromBytes(data) {
+        const keyString = hexlify(data);
+        if (PrivateKey.isDerKey(keyString)) {
+            if (PrivateKey.getAlgorithm(keyString) === "ecdsa") {
+                return new PrivateKey(PrivateKeyCrypto.fromBytesECDSA(data));
+            } else {
+                return new PrivateKey(PrivateKeyCrypto.fromBytesED25519(data));
+            }
+        }
+
+        // If the key is not DER, we assume it's a raw private key
+        // this will default to ED25519 as we dont have a way
+        // to determine the type of the key based on the raw bytes
         return new PrivateKey(PrivateKeyCrypto.fromBytes(data));
     }
 
@@ -130,6 +143,12 @@ export default class PrivateKey extends Key {
      * @returns {PrivateKey}
      */
     static fromString(text) {
+        if (PrivateKey.isDerKey(text)) {
+            return this.fromStringDer(text);
+        }
+        // If the key is not DER, we assume it's a raw private key
+        // this will default to ED25519 as we dont have a way
+        // to determine the type of the key based on the raw bytes
         return new PrivateKey(PrivateKeyCrypto.fromString(text));
     }
 
@@ -140,7 +159,17 @@ export default class PrivateKey extends Key {
      * @returns {PrivateKey}
      */
     static fromStringDer(text) {
-        return new PrivateKey(PrivateKeyCrypto.fromString(text));
+        // previous versions of the library used to accept non-der encoded private keys here
+        // and it fallbacked to PrivateKey.fromString() so we need to keep this behavior
+        if (!PrivateKey.isDerKey(text)) {
+            return PrivateKey.fromString(text);
+        }
+
+        if (PrivateKey.getAlgorithm(text) === "ecdsa") {
+            return this.fromStringECDSA(text);
+        } else {
+            return this.fromStringED25519(text);
+        }
     }
 
     /**
@@ -480,6 +509,38 @@ export default class PrivateKey extends Key {
     getRecoveryId(r, s, message) {
         return this._key.getRecoveryId(r, s, message);
     }
+
+    /**
+     * @param {string} privateKey
+     * @returns { "ecdsa" | "ed25519"}
+     */
+    static getAlgorithm(privateKey) {
+        if (!PrivateKey.isDerKey(privateKey)) {
+            throw new Error("Only der keys are supported");
+        }
+
+        const decoder = new ASN1Decoder(Uint8Array.from(decode(privateKey)));
+        decoder.read();
+        const decodedKeyType = decoder.getOidKeyTypes()[0];
+        // @ts-ignored
+        return decodedKeyType;
+    }
+
+    /**
+     * @internal
+     * @param {string} key
+     * @returns {boolean}
+     */
+    static isDerKey(key) {
+        try {
+            const data = Uint8Array.from(decode(key));
+            const decoder = new ASN1Decoder(data);
+            decoder.read(); // Attempt to read the ASN.1 structure
+            return true;
+        } catch (error) {
+            return false;
+        }
+    }
 }
 
 CACHE.setPrivateKeyConstructor((key) => new PrivateKey(key));
diff --git a/src/Wallet.js b/src/Wallet.js
index 9013ba0f9b..37998b1db7 100644
--- a/src/Wallet.js
+++ b/src/Wallet.js
@@ -46,10 +46,21 @@ export default class Wallet {
      * @param {Provider=} provider
      */
     constructor(accountId, privateKey, provider) {
-        const key =
-            typeof privateKey === "string"
-                ? PrivateKey.fromStringDer(privateKey)
-                : privateKey;
+        /*
+        TODO: deprecate on a major version 
+        the following lines were added because we didnt have
+        a way to check the algorithm of a der encoded private key
+        We need to keep the old behavior for the transition period.
+        */
+        let key;
+        if (typeof privateKey === "string" && PrivateKey.isDerKey(privateKey)) {
+            key = PrivateKey.fromStringDer(privateKey);
+        } else if (typeof privateKey === "string") {
+            // eslint-disable-next-line deprecation/deprecation
+            key = PrivateKey.fromString(privateKey);
+        } else {
+            key = privateKey;
+        }
 
         this.publicKey = key.publicKey;
         /**
diff --git a/src/util/ASN1-Decoder.js b/src/util/ASN1-Decoder.js
new file mode 100644
index 0000000000..7f97cc4fcf
--- /dev/null
+++ b/src/util/ASN1-Decoder.js
@@ -0,0 +1,256 @@
+import { proto } from "@hashgraph/proto";
+/* eslint-disable @typescript-eslint/no-unsafe-return */
+/**
+ * A class to decode ASN.1 encoded data, typically used for parsing cryptographic key data.
+ * @class
+ */
+export class ASN1Decoder {
+    /**
+     * Creates a new ASN1Decoder instance.
+     * @param {Uint8Array|ArrayBuffer} data - The ASN.1 encoded data to decode.
+     */
+    constructor(data) {
+        /**
+         * @private
+         * @type {Uint8Array}
+         */
+        this.data = new Uint8Array(data);
+
+        /**
+         * @private
+         * @type {number}
+         */
+        this.pos = 0;
+
+        /**
+         * @private
+         * @type {string[]}
+         */
+        this.oids = [];
+
+        /**
+         * @private
+         * @type {Object<string, string>}
+         */
+        this.oidMap = {
+            "1.3.132.0.10": "ecdsa",
+            "1.3.101.112": "ed25519",
+            "1.2.840.10045.2.1": "pubkey",
+        };
+
+        /**
+         * @private
+         * @type {boolean}
+         */
+        this.isPublicKey = false;
+    }
+
+    /**
+     * Reads the length field from the ASN.1 data.
+     * @private
+     * @returns {number} The length value.
+     */
+    readLength() {
+        let length = this.data[this.pos++];
+        if (length & 0x80) {
+            let numBytes = length & 0x7f;
+            length = 0;
+            for (let i = 0; i < numBytes; i++) {
+                length = (length << 8) | this.data[this.pos++];
+            }
+        }
+        return length;
+    }
+
+    /**
+     * Reads the type field from the ASN.1 data.
+     * @private
+     * @returns {number} The type value.
+     */
+    readType() {
+        return this.data[this.pos++];
+    }
+
+    /**
+     * Reads an integer value from the ASN.1 data.
+     * @private
+     * @returns {{integer: number}} Object containing the integer value.
+     */
+    readInteger() {
+        const length = this.readLength();
+        let value = 0;
+        for (let i = 0; i < length; i++) {
+            value = (value << 8) | this.data[this.pos++];
+        }
+        return { integer: value };
+    }
+
+    /**
+     * Reads an octet string from the ASN.1 data.
+     * @private
+     * @returns {{pkey: Uint8Array}} Object containing the private key data.
+     */
+    readOctetString() {
+        const length = this.readLength();
+        const value = this.data.slice(this.pos, this.pos + length);
+        this.pos += length;
+        return { pkey: value };
+    }
+
+    /**
+     * Reads a bit string from the ASN.1 data.
+     * @private
+     * @returns {{unusedBits: number, pubkey: Uint8Array}} Object containing the public key data and unused bits.
+     */
+    readBitString() {
+        const length = this.readLength();
+        const unusedBits = this.data[this.pos++]; // First byte indicates the number of unused bits
+        const value = this.data.slice(this.pos, this.pos + length - 1);
+        this.pos += length - 1;
+        return { unusedBits, pubkey: value };
+    }
+
+    /**
+     * Reads an object identifier from the ASN.1 data.
+     * @private
+     * @returns {{oid: string}} Object containing the OID as a string.
+     */
+    readObjectIdentifier() {
+        const length = this.readLength();
+        const endPos = this.pos + length;
+        const oid = [];
+        let value = 0;
+
+        // The first byte contains the first two components
+        const firstByte = this.data[this.pos++];
+        oid.push(Math.floor(firstByte / 40));
+        oid.push(firstByte % 40);
+
+        while (this.pos < endPos) {
+            const byte = this.data[this.pos++];
+            value = (value << 7) | (byte & 0x7f);
+            if (!(byte & 0x80)) {
+                oid.push(value);
+                value = 0;
+            }
+        }
+
+        const oidStr = oid.join(".");
+        this.oids.push(oidStr);
+        return { oid: oidStr }; // Return OID as a string
+    }
+
+    /**
+     * Gets the list of object identifiers found during decoding.
+     * @returns {string[]} Array of OID strings.
+     */
+    getOids() {
+        return this.oids;
+    }
+
+    /**
+     * Gets the key types corresponding to the OIDs found during decoding.
+     * @returns {string[]} Array of key type strings.
+     */
+    getOidKeyTypes() {
+        return this.oids.map((oid) => this.oidMap[oid] || "unknown");
+    }
+
+    /**
+     * Reads a sequence from the ASN.1 data.
+     * @private
+     * @returns {Array<any>} Array of decoded items in the sequence.
+     */
+    readSequence() {
+        const length = this.readLength();
+        const endPos = this.pos + length;
+        const items = []; // this would better be map or obj
+        while (this.pos < endPos) {
+            items.push(this.read());
+        }
+
+        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
+        return items;
+    }
+
+    /**
+     * Reads and decodes the next ASN.1 element.
+     * @returns {any} The decoded ASN.1 element.
+     * @throws {Error} If an unsupported type is encountered.
+     */
+    read() {
+        const type = this.readType();
+
+        switch (type) {
+            case 0x02: // INTEGER
+                return this.readInteger();
+            case 0x03: // BIT STRING FOR PUBKEY
+                return this.readBitString();
+            case 0x04: // OCTET STRING FOR PKEY
+                return this.readOctetString();
+            case 0x06: // OBJECT IDENTIFIER FOR CURVE TYPE
+                return this.readObjectIdentifier();
+            case 0x30: // SEQUENCE
+                return this.readSequence();
+            case 0xa0: // NODE TAG COULD BE TREATED AS SEQUENCE
+                return this.readSequence();
+            case 0xa1: // NODE TAG COULD BE TREATED AS SEQUENCE
+                return this.readSequence();
+            default:
+                throw new Error(`Unsupported type: ${type}`);
+        }
+    }
+
+    /**
+     * Checks if the decoded key is a public key.
+     * @returns {boolean} True if the key is a public key, false otherwise.
+     */
+    isPublicKeyType() {
+        return this.isPublicKey;
+    }
+}
+
+/**
+ * Processes a hexadecimal string representation of an ASN.1 encoded key.
+ * Checks if the input is a list of keys in hexadecimal format. If not, it decodes
+ * the ASN.1 data to extract key types and identify if the key is a public key.
+ *
+ * @param {string} keyString - The hexadecimal string representation of the ASN.1 encoded key.
+ * @returns {{keyTypes: string[], isPublicKey: boolean, isKeyListHex: boolean}} Object containing information about the decoded key.
+ */
+export const asn1DecodeStringDer = (keyString) => {
+    const isKeyListHex = isHexKeyList(keyString);
+
+    if (isKeyListHex) {
+        return { keyTypes: [], isPublicKey: false, isKeyListHex };
+    }
+
+    const signerData = Uint8Array.from(Buffer.from(keyString, "hex"));
+    const decoder = new ASN1Decoder(signerData);
+
+    decoder.read();
+    const keyTypes = decoder.getOidKeyTypes();
+    const isPublicKey = decoder.isPublicKeyType();
+
+    return {
+        keyTypes,
+        isPublicKey,
+        isKeyListHex: false,
+    };
+};
+
+/**
+ * Checks if the input is a list of keys in hexadecimal format.
+ * @param {string} hexString - The hexadecimal string representation of the ASN.1 encoded key.
+ * @returns {boolean} True if the key is a public key, false otherwise.
+ */
+const isHexKeyList = (hexString) => {
+    try {
+        const binaryData = Buffer.from(hexString, "hex");
+        const decodedData = proto.KeyList.decode(binaryData);
+
+        return !!decodedData.keys && Array.isArray(decodedData.keys);
+    } catch (error) {
+        return false;
+    }
+};
diff --git a/test/unit/PrivateKey.js b/test/unit/PrivateKey.js
index 94920e0f7b..822691c1d2 100644
--- a/test/unit/PrivateKey.js
+++ b/test/unit/PrivateKey.js
@@ -216,4 +216,58 @@ describe("PrivateKey", function () {
             expect(txSigPairMaps.length).to.equal(contents.length);
         });
     });
+
+    describe("DER encoded functions", function () {
+        it("should return true if the key is a DER key", function () {
+            const derKey = PrivateKey.generateECDSA().toStringDer();
+            expect(PrivateKey.isDerKey(derKey)).to.be.true;
+        });
+
+        it("should return false if the key is not a DER key", function () {
+            const derKey = PrivateKey.generateECDSA().toStringRaw();
+            expect(PrivateKey.isDerKey(derKey)).to.be.false;
+        });
+
+        it("should generate key from der string on ecdsa", function () {
+            const derKey = PrivateKey.generateECDSA().toStringDer();
+            const key = PrivateKey.fromStringDer(derKey);
+            expect(key.toStringDer()).to.equal(derKey);
+        });
+
+        it("should generate key from der string on ed25519", function () {
+            const derKey = PrivateKey.generateED25519().toStringDer();
+            const key = PrivateKey.fromStringDer(derKey);
+            expect(key.toStringDer()).to.equal(derKey);
+        });
+
+        it("should generate key from der bytes on ecdsa", function () {
+            const derKey = PrivateKey.generateECDSA().toBytesDer();
+            const key = PrivateKey.fromBytes(derKey);
+
+            expect(key.toBytesDer()).to.deep.equal(derKey);
+        });
+
+        it("should generate key from der bytes on ed25519", function () {
+            const derKey = PrivateKey.generateED25519().toBytesDer();
+            const key = PrivateKey.fromBytes(derKey);
+            expect(key.toBytesDer()).to.deep.equal(derKey);
+        });
+
+        it("should get ecdsa algorithm from der key", function () {
+            const derKey = PrivateKey.generateECDSA().toStringDer();
+            expect(PrivateKey.getAlgorithm(derKey)).to.equal("ecdsa");
+        });
+
+        it("should get ed25519algorithm from der key", function () {
+            const derKey = PrivateKey.generateED25519().toStringDer();
+            expect(PrivateKey.getAlgorithm(derKey)).to.equal("ed25519");
+        });
+
+        it("should throw error if invalid der key", function () {
+            const derKey = "invalid";
+            expect(() => PrivateKey.getAlgorithm(derKey)).to.throw(
+                "Only der keys are supported",
+            );
+        });
+    });
 });
diff --git a/test/unit/TokenInfo.js b/test/unit/TokenInfo.js
index 24b553cd50..94d71dd47e 100644
--- a/test/unit/TokenInfo.js
+++ b/test/unit/TokenInfo.js
@@ -35,25 +35,25 @@ describe("TokenInfo", function () {
             totalSupply: Long.fromNumber(1000000),
             treasuryAccountId: treasuryId,
             adminKey: PrivateKey.fromStringDer(
-                "302a300506032b65700321008a307a33fddd469a097c4a1b392149206c854d6f3348a80d9b364d8a1c9b3b72",
+                "302e0201010420a6170a6aa6389a5bd3a3a8f9375f57bd91aa7f7d8b8b46ce0b702e000a21a5fea00706052b8104000a",
             ),
             kycKey: PrivateKey.fromStringDer(
-                "302a300506032b65700321009d61d0b816743dd8540d3be1250607714a5a9be3ec428e09fee218204d598849",
+                "302e0201010420a6170a6aa6389a5bd3a3a8f9375f57bd91aa7f7d8b8b46ce0b702e000a21a5fea00706052b8104000a",
             ),
             freezeKey: PrivateKey.fromStringDer(
-                "302a300506032b6570032100e613d28cd56db4ef8f2bb34f2e4b23dbf25c059c7f4b0dbff8e0c384195186f3",
+                "302e0201010420a6170a6aa6389a5bd3a3a8f9375f57bd91aa7f7d8b8b46ce0b702e000a21a5fea00706052b8104000a",
             ),
             pauseKey: PrivateKey.fromStringDer(
-                "302a300506032b6570032100120763d691118ef319da0d97a5df27c671a1f8068d30d452cd7d9dbcee7ebc0fe",
+                "302e0201010420a6170a6aa6389a5bd3a3a8f9375f57bd91aa7f7d8b8b46ce0b702e000a21a5fea00706052b8104000a",
             ),
             wipeKey: PrivateKey.fromStringDer(
-                "302a300506032b6570032100bf79bc0e2e469695ba4b18a35a8e67c8d691c746d93e2e31b8ab03a7cdabb617",
+                "302e0201010420a6170a6aa6389a5bd3a3a8f9375f57bd91aa7f7d8b8b46ce0b702e000a21a5fea00706052b8104000a",
             ),
             supplyKey: PrivateKey.fromStringDer(
-                "302a300506032b6570032100ec0151b38dacf4d3d8a9665fca4ac78f9a56a1d0c73551c433ea344e56a246b3",
+                "302e0201010420a6170a6aa6389a5bd3a3a8f9375f57bd91aa7f7d8b8b46ce0b702e000a21a5fea00706052b8104000a",
             ),
             feeScheduleKey: PrivateKey.fromStringDer(
-                "302a300506032b65700321002b7345f544ce72cae9f5602c28ee5a9fb2f4dce65bf089c20d6cc0802be5b25a",
+                "302e0201010420a6170a6aa6389a5bd3a3a8f9375f57bd91aa7f7d8b8b46ce0b702e000a21a5fea00706052b8104000a",
             ),
             defaultFreezeStatus: false,
             defaultKycStatus: true,
@@ -75,7 +75,7 @@ describe("TokenInfo", function () {
             maxSupply: Long.fromNumber(2000000),
             ledgerId: LedgerId.fromString("mainnet"),
             metadataKey: PrivateKey.fromStringDer(
-                "302a300506032b65700321003eae1f4596369b40ed7736644e3a453a68c33e5eac99949aa3d11693ed97b97b",
+                "302e0201010420a6170a6aa6389a5bd3a3a8f9375f57bd91aa7f7d8b8b46ce0b702e000a21a5fea00706052b8104000a",
             ),
             metadata: new Uint8Array([1, 2, 3, 4]),
         });


Reviews:
user: login: ivaylogarnev-limechain
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T06:09:45Z
user: login: ivaylonikolov7
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T12:45:16Z
user: login: venilinvasilev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:03:25Z
user: login: ivaylonikolov7
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:12:12Z
user: login: venilinvasilev
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T06:46:11Z

Review comments:
diff_hunk: @@ -1,12 +1,17 @@
-import { Wallet, LocalProvider } from "../../src/index.js";
+import {
+    Wallet,
+    LocalProvider,
+    PrivateKey,
+    AccountId,
+} from "../../src/index.js";
 
 describe("LocalWallet", function () {
     it("can fetch wallet's info", async function () {
-        const wallet = new Wallet(
-            process.env.OPERATOR_ID,
+        const operatorId = AccountId.fromString(process.env.OPERATOR_ID);
+        const operatorKey = PrivateKey.fromStringED25519(
path: test/integration/LocalWalletTest.js
user: login: ivaylogarnev-limechain
body: What if the user key is in ECDSA format? Wouldn't it be better to use the reworked `fromStringDer` or `fromString` methods here?
created_at: 2025-04-30T06:09:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,17 @@
-import { Wallet, LocalProvider } from "../../src/index.js";
+import {
+    Wallet,
+    LocalProvider,
+    PrivateKey,
+    AccountId,
+} from "../../src/index.js";
 
 describe("LocalWallet", function () {
     it("can fetch wallet's info", async function () {
-        const wallet = new Wallet(
-            process.env.OPERATOR_ID,
+        const operatorId = AccountId.fromString(process.env.OPERATOR_ID);
+        const operatorKey = PrivateKey.fromStringED25519(
path: test/integration/LocalWalletTest.js
user: login: ivaylonikolov7
body: Our integration tests don't work with ECDSA unless we change the BaseIntegrationTestEnv anyway. The code I've sent is from the BaseIntegrationTestEnv. 

<img width="579" alt="image" src="https://github.com/user-attachments/assets/7713caf9-c679-4bce-adc3-350a68db249e" />

created_at: 2025-05-02T12:45:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -480,6 +509,43 @@ export default class PrivateKey extends Key {
     getRecoveryId(r, s, message) {
         return this._key.getRecoveryId(r, s, message);
     }
+
+    /**
+     * @param {string} privateKey
+     * @returns { "ecdsa" | "ed25519"}
+     */
+    static getAlgorithm(privateKey) {
+        const decoder = new ASN1Decoder(Uint8Array.from(decode(privateKey)));
+        decoder.read();
+        const supportedKeyTypes = ["ecdsa", "ed25519"];
+        const decodedKeyType = decoder.getOidKeyTypes()[0];
+
+        if (!PrivateKey.isDerKey(privateKey)) {
+            throw new Error("Only der keys are supported");
+        }
+        if (!supportedKeyTypes.includes(decodedKeyType)) {
path: src/PrivateKey.js
user: login: venilinvasilev
body: Why is this check after the isDerKey one? What is the intention, need some context here for the case when the user inputs a plain string that is not a real key shouldn't be for user perspective clearer that this key is not supported instead of showing that only der keys are supported?
created_at: 2025-05-08T14:03:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -480,6 +509,43 @@ export default class PrivateKey extends Key {
     getRecoveryId(r, s, message) {
         return this._key.getRecoveryId(r, s, message);
     }
+
+    /**
+     * @param {string} privateKey
+     * @returns { "ecdsa" | "ed25519"}
+     */
+    static getAlgorithm(privateKey) {
+        const decoder = new ASN1Decoder(Uint8Array.from(decode(privateKey)));
+        decoder.read();
+        const supportedKeyTypes = ["ecdsa", "ed25519"];
+        const decodedKeyType = decoder.getOidKeyTypes()[0];
+
+        if (!PrivateKey.isDerKey(privateKey)) {
+            throw new Error("Only der keys are supported");
+        }
+        if (!supportedKeyTypes.includes(decodedKeyType)) {
path: src/PrivateKey.js
user: login: ivaylonikolov7
body: Yeah but if someone uses it incorrectly it might be a breaking change for him. :( 
created_at: 2025-05-08T14:12:12Z
author_association: CONTRIBUTOR

PR comments:
user: login: ivaylogarnev-limechain
created_at: 2025-04-30T06:14:53Z
body: 1. I think it would be beneficial to add such a checks for the `PrivateKey.fromStringED25519/fromStringECDSA` methods as well.
2. Also isn't this applicable for the `PublicKey` as well?
author_association: CONTRIBUTOR
user: login: lfdt-bot
created_at: 2025-05-02T12:40:09Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/hiero-client-sdks/pr-checks/af6d007a-6d4a-43db-9a07-f9998c0cbd68?refs=d067e340e0c3ea4cb207c9311e24ab76cdde1337&source=prChecksComment)

:white_check_mark: **license/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/hiero-client-sdks/pr-checks/af6d007a-6d4a-43db-9a07-f9998c0cbd68/license?refs=d067e340e0c3ea4cb207c9311e24ab76cdde1337&source=prChecksComment)



author_association: CONTRIBUTOR
user: login: ivaylonikolov7
created_at: 2025-05-02T12:42:05Z
body: Regarding your comments @ivaylogarnev-limechain:
We dont have a way to check if a key is `ED25519` or `ECDSA` if it's not DER encoded so a `fromStringED25519` and `fromStringECDSA` wouldn't be possible.  

Or did you mean to add checks if it's der encoded in the `fromStringED25519` and `fromStringECDSA`? 
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-07T17:10:40Z
body: ## [Codecov](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger) Report
Attention: Patch coverage is `87.05502%` with `40 lines` in your changes missing coverage. Please review.

| [Files with missing lines](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger) | Patch % | Lines |
|---|---|---|
| [src/util/ASN1-Decoder.js](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?src=pr&el=tree&filepath=src%2Futil%2FASN1-Decoder.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger#diff-c3JjL3V0aWwvQVNOMS1EZWNvZGVyLmpz) | 85.58% | [33 Missing :warning: ](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger) |
| [src/PrivateKey.js](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?src=pr&el=tree&filepath=src%2FPrivateKey.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger#diff-c3JjL1ByaXZhdGVLZXkuanM=) | 90.76% | [6 Missing :warning: ](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger) |
| [src/Wallet.js](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?src=pr&el=tree&filepath=src%2FWallet.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger#diff-c3JjL1dhbGxldC5qcw==) | 93.33% | [1 Missing :warning: ](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger) |

| [Files with missing lines](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger) | Coverage Δ | |
|---|---|---|
| [src/Wallet.js](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?src=pr&el=tree&filepath=src%2FWallet.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger#diff-c3JjL1dhbGxldC5qcw==) | `75.18% <93.33%> (+0.67%)` | :arrow_up: |
| [src/PrivateKey.js](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?src=pr&el=tree&filepath=src%2FPrivateKey.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger#diff-c3JjL1ByaXZhdGVLZXkuanM=) | `95.19% <90.76%> (+0.85%)` | :arrow_up: |
| [src/util/ASN1-Decoder.js](https://app.codecov.io/gh/hiero-ledger/hiero-sdk-js/pull/3068?src=pr&el=tree&filepath=src%2Futil%2FASN1-Decoder.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hiero-ledger#diff-c3JjL3V0aWwvQVNOMS1EZWNvZGVyLmpz) | `85.58% <85.58%> (ø)` | |

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: venilinvasilev
created_at: 2025-05-08T13:57:03Z
body: Looks good, but how about adding tests to cover the new functionallity?
author_association: CONTRIBUTOR
user: login: ivaylonikolov7
created_at: 2025-05-08T14:18:49Z
body: > Looks good, but how about adding tests to cover the new functionallity?

we already have test vectors in `test/unit/ecdsaprivatekey.js` and `test/unit/ed25519privatekey`.

Did you mean for the `Asn1Decoder.js`?
author_association: CONTRIBUTOR
user: login: venilinvasilev
created_at: 2025-05-08T15:13:11Z
body: > > Looks good, but how about adding tests to cover the new functionallity?
> 
> we already have test vectors in `test/unit/ecdsaprivatekey.js` and `test/unit/ed25519privatekey`.
> 
> Did you mean for the `Asn1Decoder.js`?

I mean for the new methods in the PrivateKey class (and Asn1Decoder if applicable).
author_association: CONTRIBUTOR