diff --git a/.changeset/fix-privkey-prefix.md b/.changeset/fix-privkey-prefix.md new file mode 100644 index 0000000..a420ec6 --- /dev/null +++ b/.changeset/fix-privkey-prefix.md @@ -0,0 +1,5 @@ +--- +"@offckb/cli": patch +--- + +fix(cli): standardize private key inputs and fix 0x prefix parsing error (#422) diff --git a/src/sdk/ckb.ts b/src/sdk/ckb.ts index 5276de7..1c07cdc 100644 --- a/src/sdk/ckb.ts +++ b/src/sdk/ckb.ts @@ -2,7 +2,7 @@ // to replace lumos with ccc import { ccc, ClientPublicMainnet, ClientPublicTestnet, OutPointLike, Script } from '@ckb-ccc/core'; -import { isValidNetworkString } from '../util/validator'; +import { isValidNetworkString, normalizePrivKey } from '../util/validator'; import { networks } from './network'; import { buildCCCDevnetKnownScripts } from '../scripts/private'; import { Migration } from '../deploy/migration'; @@ -81,7 +81,8 @@ export class CKB { } buildSigner(privateKey: HexString) { - const signer = new ccc.SignerCkbPrivateKey(this.client, privateKey); + const normalizedKey = normalizePrivKey(privateKey); + const signer = new ccc.SignerCkbPrivateKey(this.client, normalizedKey); return signer; } diff --git a/src/util/validator.ts b/src/util/validator.ts index 5566e52..53e9fe7 100644 --- a/src/util/validator.ts +++ b/src/util/validator.ts @@ -73,3 +73,43 @@ export function isValidVersion(version: unknown): boolean { // Test the version against the regex return versionRegex.test(version); } + +export function normalizePrivKey(privKey: string): string { + // Trim surrounding whitespaces + let key = privKey ? privKey.trim() : ''; + + if (!key) { + throw new Error('Private key is required.'); + } + + // Strip surrounding quotes + if (key.startsWith('"') && key.endsWith('"')) { + key = key.slice(1, -1); + } + if (key.startsWith("'") && key.endsWith("'")) { + key = key.slice(1, -1); + } + + // Trim again to normalize whitespace that was inside surrounding quotes + key = key.trim(); + + // Remove standard 0x/0X prefix if it exists manually for normalization + if (/^0x/i.test(key)) { + key = key.slice(2); + } + + // Validate only hex characters are left + if (!/^[0-9a-fA-F]+$/.test(key)) { + throw new Error('Invalid private key: contains non-hexadecimal characters.'); + } + + // Enforce exactly 32 bytes length + if (key.length !== 64) { + throw new Error( + `Invalid private key length: expected 32 bytes (64 hex characters), but got ${key.length} characters (excluding 0x prefix).`, + ); + } + + // Return the formally strictly padded ckb format `0x` string + return '0x' + key; +} diff --git a/tests/validator.test.ts b/tests/validator.test.ts new file mode 100644 index 0000000..9b57b26 --- /dev/null +++ b/tests/validator.test.ts @@ -0,0 +1,46 @@ +import { normalizePrivKey } from '../src/util/validator'; + +describe('normalizePrivKey', () => { + const validHex64 = '1234567812345678123456781234567812345678123456781234567812345678'; + + it('should throw an error for empty or formatting values', () => { + expect(() => normalizePrivKey('')).toThrow('Private key is required.'); + expect(() => normalizePrivKey(' ')).toThrow('Private key is required.'); + }); + + it('should accept a 64-character hex string without 0x prefix', () => { + const key = validHex64; + expect(normalizePrivKey(key)).toBe('0x' + key); + }); + + it('should accept a 64-character hex string with 0x prefix', () => { + const key = '0x' + validHex64; + expect(normalizePrivKey(key)).toBe('0x' + validHex64); + }); + + it('should accept a 64-character hex string with 0X prefix (case-insensitive)', () => { + const key = '0X' + validHex64; + expect(normalizePrivKey(key)).toBe('0x' + validHex64); + }); + + it('should correctly trim spaces inside quotes', () => { + const key = `' 0x${validHex64} '`; + expect(normalizePrivKey(key)).toBe('0x' + validHex64); + + const key2 = `" 0X${validHex64} "`; + expect(normalizePrivKey(key2)).toBe('0x' + validHex64); + }); + + it('should throw an error if the key contains non-hexadecimal characters', () => { + const invalidHex = validHex64.slice(0, -1) + 'G'; // Replace last char with 'G' (invalid hex) + expect(() => normalizePrivKey(invalidHex)).toThrow('Invalid private key: contains non-hexadecimal characters.'); + }); + + it('should throw an error if the key length is incorrect', () => { + const shortKey = validHex64.slice(0, 62); + expect(() => normalizePrivKey(shortKey)).toThrow('Invalid private key length'); + + const longKey = validHex64 + '12'; + expect(() => normalizePrivKey(longKey)).toThrow('Invalid private key length'); + }); +});