Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add validation for public key #2841

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions packages/cryptography/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
17 changes: 17 additions & 0 deletions packages/cryptography/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 25 additions & 3 deletions packages/cryptography/src/EcdsaPublicKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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) {
Expand All @@ -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");
}
}

/**
Expand Down
28 changes: 24 additions & 4 deletions packages/cryptography/src/Ed25519PublicKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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");
}
}

/**
Expand Down
38 changes: 38 additions & 0 deletions test/unit/PublicKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
});
});
Loading