From e62a653718e57d52e092ab58ba5800545fce032e Mon Sep 17 00:00:00 2001 From: RetricSu Date: Fri, 3 Apr 2026 23:32:53 +0800 Subject: [PATCH 1/3] fix(cli): normalize private key and 0x prefix validation This fixes issue #422 by catching whitespace and padding issues, validating length and properly standardizing the 0x prefix to prevent confusing downstream ckb-ccc cryptography errors. --- src/sdk/ckb.ts | 5 +++-- src/util/validator.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) 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..ef7d90e 100644 --- a/src/util/validator.ts +++ b/src/util/validator.ts @@ -73,3 +73,38 @@ export function isValidVersion(version: unknown): boolean { // Test the version against the regex return versionRegex.test(version); } + +export function normalizePrivKey(privKey: string): string { + if (!privKey) return privKey; + + // Trim surrounding whitespaces + let key = privKey.trim(); + + // Strip surrounding quotes + if (key.startsWith('"') && key.endsWith('"')) { + key = key.slice(1, -1); + } + if (key.startsWith("'") && key.endsWith("'")) { + key = key.slice(1, -1); + } + + // Remove standard 0x prefix if exists manually for normalization + if (key.startsWith('0x')) { + 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; +} From 679d87eaf373e2e622f9c29b3c7fb61502bedebf Mon Sep 17 00:00:00 2001 From: RetricSu Date: Fri, 3 Apr 2026 23:35:10 +0800 Subject: [PATCH 2/3] chore: add changeset for private key parsing fix --- .changeset/fix-privkey-prefix.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-privkey-prefix.md 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) From e35bb26ad99f5d94cd4afb885d1c3e4a848b30ff Mon Sep 17 00:00:00 2001 From: RetricSu Date: Fri, 3 Apr 2026 23:43:45 +0800 Subject: [PATCH 3/3] fix(cli): address copilot review feedback --- src/util/validator.ts | 15 +++++++++----- tests/validator.test.ts | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 tests/validator.test.ts diff --git a/src/util/validator.ts b/src/util/validator.ts index ef7d90e..53e9fe7 100644 --- a/src/util/validator.ts +++ b/src/util/validator.ts @@ -75,10 +75,12 @@ export function isValidVersion(version: unknown): boolean { } export function normalizePrivKey(privKey: string): string { - if (!privKey) return privKey; - // Trim surrounding whitespaces - let key = privKey.trim(); + let key = privKey ? privKey.trim() : ''; + + if (!key) { + throw new Error('Private key is required.'); + } // Strip surrounding quotes if (key.startsWith('"') && key.endsWith('"')) { @@ -88,8 +90,11 @@ export function normalizePrivKey(privKey: string): string { key = key.slice(1, -1); } - // Remove standard 0x prefix if exists manually for normalization - if (key.startsWith('0x')) { + // 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); } 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'); + }); +});