diff --git a/packages/cryptography/package.json b/packages/cryptography/package.json index e20e17446..75dd9022c 100644 --- a/packages/cryptography/package.json +++ b/packages/cryptography/package.json @@ -59,6 +59,7 @@ "last 1 Firefox versions" ], "dependencies": { + "@noble/curves": "^1.8.1", "asn1js": "^3.0.5", "bignumber.js": "^9.1.1", "bn.js": "^5.2.1", diff --git a/packages/cryptography/pnpm-lock.yaml b/packages/cryptography/pnpm-lock.yaml index bd6816e41..a75eb5cfe 100644 --- a/packages/cryptography/pnpm-lock.yaml +++ b/packages/cryptography/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@noble/curves': + specifier: ^1.8.1 + version: 1.8.1 asn1js: specifier: ^3.0.5 version: 3.0.5 @@ -1215,6 +1218,14 @@ packages: '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + '@noble/curves@1.8.1': + resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.7.1': + resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} + engines: {node: ^14.21.3 || >=16} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -6123,6 +6134,12 @@ snapshots: dependencies: eslint-scope: 5.1.1 + '@noble/curves@1.8.1': + dependencies: + '@noble/hashes': 1.7.1 + + '@noble/hashes@1.7.1': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 diff --git a/packages/cryptography/src/EcdsaPublicKey.js b/packages/cryptography/src/EcdsaPublicKey.js index 30863c618..9c2482f78 100644 --- a/packages/cryptography/src/EcdsaPublicKey.js +++ b/packages/cryptography/src/EcdsaPublicKey.js @@ -4,6 +4,7 @@ import { arrayEqual } from "./util/array.js"; import * as hex from "./encoding/hex.js"; import * as ecdsa from "./primitive/ecdsa.js"; import { keccak256 } from "./primitive/keccak.js"; +import { secp256k1 } from "@noble/curves/secp256k1"; import elliptic from "elliptic"; const ec = new elliptic.ec("secp256k1"); @@ -92,12 +93,15 @@ export default class EcdsaPublicKey extends Key { `cannot decode ECDSA private key data from DER format`, ); } - return new EcdsaPublicKey(ecdsaPublicKeyBytes); + + return EcdsaPublicKey.fromBytesRaw(ecdsaPublicKeyBytes); } /** - * @param {Uint8Array} data + * Creates an ECDSA public key from raw bytes after validating the input + * @param {Uint8Array} data - Raw byte data for the public key * @returns {EcdsaPublicKey} + * @throws {BadKeyError} If the key is invalid or has an incorrect length */ static fromBytesRaw(data) { if (data.length != 33) { @@ -106,7 +110,25 @@ export default class EcdsaPublicKey extends Key { ); } - return new EcdsaPublicKey(data); + const EMPTY_BUFFER_33_BYTES = Buffer.alloc(33); + const EMPTY_BUFFER_65_BYTES = Buffer.alloc(65); + const bufferData = Buffer.from(data); + + // Check for empty buffers (as per HIP-540) + if ( + EMPTY_BUFFER_33_BYTES.equals(bufferData) || + EMPTY_BUFFER_65_BYTES.equals(bufferData) + ) { + return new EcdsaPublicKey(data); + } + + // Attempt to validate the key using secp256k1 library + try { + secp256k1.ProjectivePoint.fromHex(data); + return new EcdsaPublicKey(data); + } catch { + throw new BadKeyError("invalid public key"); + } } /** diff --git a/packages/cryptography/src/Ed25519PublicKey.js b/packages/cryptography/src/Ed25519PublicKey.js index b6606d12a..64dbc3085 100644 --- a/packages/cryptography/src/Ed25519PublicKey.js +++ b/packages/cryptography/src/Ed25519PublicKey.js @@ -4,6 +4,7 @@ import nacl from "tweetnacl"; import { arrayEqual } from "./util/array.js"; import * as hex from "./encoding/hex.js"; import forge from "node-forge"; +import { ed25519 } from "@noble/curves/ed25519"; const derPrefix = "302a300506032b6570032100"; const derPrefixBytes = hex.decode(derPrefix); @@ -77,21 +78,40 @@ export default class Ed25519PublicKey extends Key { ); } - return new Ed25519PublicKey(publicKey); + return Ed25519PublicKey.fromBytesRaw(publicKey); } /** - * @param {Uint8Array} data + * Creates an Ed25519 public key from raw bytes after validating the input + * @param {Uint8Array} data - Raw byte data for the public key * @returns {Ed25519PublicKey} + * @throws {BadKeyError} If the key is invalid or has an incorrect length */ static fromBytesRaw(data) { - if (data.length != 32) { + // Check length first + if (data.length !== 32) { throw new BadKeyError( `invalid public key length: ${data.length} bytes`, ); } - return new Ed25519PublicKey(data); + // Create a buffer from the input data + const bufferData = Buffer.from(data); + + // Check for empty buffer (all zeros) + // SEE HIP-540 + const emptyBuffer = Buffer.alloc(33); + if (bufferData.equals(emptyBuffer)) { + return new Ed25519PublicKey(data); + } + + // Attempt to validate the key using ed25519 library + try { + ed25519.ExtendedPoint.fromHex(data); + return new Ed25519PublicKey(data); + } catch { + throw new BadKeyError("invalid public key"); + } } /** diff --git a/test/unit/PublicKey.js b/test/unit/PublicKey.js index f659ecad5..938f9f937 100644 --- a/test/unit/PublicKey.js +++ b/test/unit/PublicKey.js @@ -106,4 +106,42 @@ describe("PublicKey", function () { const ed25519PublicKey1 = PublicKey.fromString(PUBLIC_KEY_DER1); expect(ed25519PublicKey1.toStringRaw()).to.be.equal(PUBLIC_KEY1); }); + + it("should throw error when creating ED25519 key from invalid bytes", function () { + const CURVE_ORDER_ED25519 = + 7237005577332262213973186563042994240857116359379907606001950938285454250989n; + const invalidKey = (CURVE_ORDER_ED25519 + 1n).toString(16); + const invalidKeyBuffer = Buffer.from(invalidKey, "hex"); + const invalidPrivateKeyBytes = Uint8Array.from(invalidKeyBuffer); + + expect(() => { + PublicKey.fromBytesED25519(invalidPrivateKeyBytes); + }).to.throw; + }); + + it("should throw error when creating ECDSA key from invalid bytes", function () { + const CURVE_ORDER_ECDSA = + 115792089237316195423570985008687907852837564279074904382605163141518161494337n; + const invalidKey = (CURVE_ORDER_ECDSA + 1n).toString(16); + const invalidKeyBuffer = Buffer.from(invalidKey, "hex"); + const invalidPrivateKeyBytes = Uint8Array.from(invalidKeyBuffer); + + expect(() => { + PublicKey.fromBytesECDSA(invalidPrivateKeyBytes); + }).to.throw; + }); + + it("should be able to create ECDSA key from valid bytes", function () { + const validPubKey = PrivateKey.generateECDSA().publicKey; + const bytes = validPubKey.toBytes(); + const publicKey = PublicKey.fromBytes(bytes); + expect(publicKey.toString()).to.be.equal(validPubKey.toString()); + }); + + it("should be able to create ED25519 key from valid bytes", function () { + const validPubKey = PrivateKey.generateED25519().publicKey; + const bytes = validPubKey.toBytes(); + const publicKey = PublicKey.fromBytes(bytes); + expect(publicKey.toString()).to.be.equal(validPubKey.toString()); + }); });