diff --git a/packages/library/src/elements/validate.ts b/packages/library/src/elements/validate.ts index 2839425..8fd3651 100644 --- a/packages/library/src/elements/validate.ts +++ b/packages/library/src/elements/validate.ts @@ -6,8 +6,9 @@ import { ErrCurrencyRequired, ErrDestinationRequired, ErrInvalidCurrency, - ErrInvalidMode + ErrInvalidMode, } from '../errors'; +import { PublicKey } from '../keys'; /** * Validates the properties of the given `ElementOptions` for intents. @@ -34,6 +35,7 @@ function validateIntentOptions(intent: ElementOptions) { * @throws {ErrAmountRequired} If the `amount` property is undefined. * @throws {ErrCurrencyRequired} If the `currency` property is undefined. * @throws {ErrInvalidCurrency} If the `currency` property is not a valid currency. + * @throws {ErrInvalidAddress} If the `destination` property is not a valid base58 address. */ function validatePaymentRequestOptions(intent: ElementOptions) { if (intent.destination === undefined) { @@ -51,6 +53,9 @@ function validatePaymentRequestOptions(intent: ElementOptions) { if (!isValidCurrency(intent.currency)) { throw ErrInvalidCurrency(); } + + // Validate that the destination is a valid address. + PublicKey.fromBase58(intent.destination); } /** diff --git a/packages/library/src/errors.ts b/packages/library/src/errors.ts index da6aceb..d26b7fc 100644 --- a/packages/library/src/errors.ts +++ b/packages/library/src/errors.ts @@ -6,6 +6,7 @@ const ErrInvalidCurrency = () => new Error("invalid currency"); const ErrUnexpectedError = () => new Error("unexpected error"); const ErrAmbiguousNonce = () => new Error("cannot derive nonce from both clientSecret and idempotencyKey"); const ErrInvalidMode = () => new Error(`invalid mode`); +const ErrInvalidAddress = () => new Error("invalid address"); export { ErrInvalidSize, @@ -16,4 +17,5 @@ export { ErrUnexpectedError, ErrAmbiguousNonce, ErrInvalidMode, + ErrInvalidAddress, }; diff --git a/packages/library/src/keys/publickey.ts b/packages/library/src/keys/publickey.ts index 0169b20..12ce96f 100644 --- a/packages/library/src/keys/publickey.ts +++ b/packages/library/src/keys/publickey.ts @@ -1,6 +1,11 @@ import bs58 from "bs58"; import { Buffer } from "buffer"; +import { ErrInvalidAddress } from "../errors"; + +const ED25519_PUBLIC_KEY_LENGTH = 32; // Length of ED25519 public key in bytes +const BASE_58_ALPHABET = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/; + /** * Represents a public key and provides utility methods for its manipulation and conversion. */ @@ -13,6 +18,9 @@ class PublicKey { * @param publicKey - The raw public key as a Uint8Array. */ constructor(publicKey: Uint8Array) { + if (publicKey.length !== ED25519_PUBLIC_KEY_LENGTH) { + throw ErrInvalidAddress(); + } this.publicKey = publicKey; } @@ -23,7 +31,17 @@ class PublicKey { * @returns A new PublicKey instance. */ static fromBase58(base58: string) { - return new PublicKey(bs58.decode(base58)); + if (!base58.match(BASE_58_ALPHABET)) { + throw ErrInvalidAddress(); + } + + const decodedBuffer = bs58.decode(base58); + + if (decodedBuffer.length !== ED25519_PUBLIC_KEY_LENGTH) { + throw ErrInvalidAddress(); + } + + return new PublicKey(decodedBuffer); } /** diff --git a/packages/library/test/publickey.test.ts b/packages/library/test/publickey.test.ts index ca48935..f80087e 100644 --- a/packages/library/test/publickey.test.ts +++ b/packages/library/test/publickey.test.ts @@ -32,4 +32,13 @@ describe('PublicKey', function () { expect(key2.toBuffer()).to.have.length(32); expect(key2.toBase58()).to.eq('11111111111111111111111111111111'); }); + + it('throws ErrInvalidAddress for invalid input', () => { + expect(() => new PublicKey(new Uint8Array(31))).to.throw(); // too short + expect(() => new PublicKey(new Uint8Array(33))).to.throw(); // too long + expect(() => PublicKey.fromBase58('hello, world')).to.throw(); // invalid base58 character + expect(() => PublicKey.fromBase58('O0l1VBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3')).to.throw(); // invalid base58 character + expect(() => PublicKey.fromBase58('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz123')).to.throw(); // too long + expect(() => PublicKey.fromBase58('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz')).to.throw(); // too short + }); }); \ No newline at end of file