Skip to content

Commit 8da140d

Browse files
authored
fix(cli): standardize private key inputs and fix 0x prefix parsing error (#423)
* 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. * chore: add changeset for private key parsing fix * fix(cli): address copilot review feedback
1 parent a5592dc commit 8da140d

4 files changed

Lines changed: 94 additions & 2 deletions

File tree

.changeset/fix-privkey-prefix.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@offckb/cli": patch
3+
---
4+
5+
fix(cli): standardize private key inputs and fix 0x prefix parsing error (#422)

src/sdk/ckb.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// to replace lumos with ccc
33

44
import { ccc, ClientPublicMainnet, ClientPublicTestnet, OutPointLike, Script } from '@ckb-ccc/core';
5-
import { isValidNetworkString } from '../util/validator';
5+
import { isValidNetworkString, normalizePrivKey } from '../util/validator';
66
import { networks } from './network';
77
import { buildCCCDevnetKnownScripts } from '../scripts/private';
88
import { Migration } from '../deploy/migration';
@@ -81,7 +81,8 @@ export class CKB {
8181
}
8282

8383
buildSigner(privateKey: HexString) {
84-
const signer = new ccc.SignerCkbPrivateKey(this.client, privateKey);
84+
const normalizedKey = normalizePrivKey(privateKey);
85+
const signer = new ccc.SignerCkbPrivateKey(this.client, normalizedKey);
8586
return signer;
8687
}
8788

src/util/validator.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,43 @@ export function isValidVersion(version: unknown): boolean {
7373
// Test the version against the regex
7474
return versionRegex.test(version);
7575
}
76+
77+
export function normalizePrivKey(privKey: string): string {
78+
// Trim surrounding whitespaces
79+
let key = privKey ? privKey.trim() : '';
80+
81+
if (!key) {
82+
throw new Error('Private key is required.');
83+
}
84+
85+
// Strip surrounding quotes
86+
if (key.startsWith('"') && key.endsWith('"')) {
87+
key = key.slice(1, -1);
88+
}
89+
if (key.startsWith("'") && key.endsWith("'")) {
90+
key = key.slice(1, -1);
91+
}
92+
93+
// Trim again to normalize whitespace that was inside surrounding quotes
94+
key = key.trim();
95+
96+
// Remove standard 0x/0X prefix if it exists manually for normalization
97+
if (/^0x/i.test(key)) {
98+
key = key.slice(2);
99+
}
100+
101+
// Validate only hex characters are left
102+
if (!/^[0-9a-fA-F]+$/.test(key)) {
103+
throw new Error('Invalid private key: contains non-hexadecimal characters.');
104+
}
105+
106+
// Enforce exactly 32 bytes length
107+
if (key.length !== 64) {
108+
throw new Error(
109+
`Invalid private key length: expected 32 bytes (64 hex characters), but got ${key.length} characters (excluding 0x prefix).`,
110+
);
111+
}
112+
113+
// Return the formally strictly padded ckb format `0x` string
114+
return '0x' + key;
115+
}

tests/validator.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { normalizePrivKey } from '../src/util/validator';
2+
3+
describe('normalizePrivKey', () => {
4+
const validHex64 = '1234567812345678123456781234567812345678123456781234567812345678';
5+
6+
it('should throw an error for empty or formatting values', () => {
7+
expect(() => normalizePrivKey('')).toThrow('Private key is required.');
8+
expect(() => normalizePrivKey(' ')).toThrow('Private key is required.');
9+
});
10+
11+
it('should accept a 64-character hex string without 0x prefix', () => {
12+
const key = validHex64;
13+
expect(normalizePrivKey(key)).toBe('0x' + key);
14+
});
15+
16+
it('should accept a 64-character hex string with 0x prefix', () => {
17+
const key = '0x' + validHex64;
18+
expect(normalizePrivKey(key)).toBe('0x' + validHex64);
19+
});
20+
21+
it('should accept a 64-character hex string with 0X prefix (case-insensitive)', () => {
22+
const key = '0X' + validHex64;
23+
expect(normalizePrivKey(key)).toBe('0x' + validHex64);
24+
});
25+
26+
it('should correctly trim spaces inside quotes', () => {
27+
const key = `' 0x${validHex64} '`;
28+
expect(normalizePrivKey(key)).toBe('0x' + validHex64);
29+
30+
const key2 = `" 0X${validHex64} "`;
31+
expect(normalizePrivKey(key2)).toBe('0x' + validHex64);
32+
});
33+
34+
it('should throw an error if the key contains non-hexadecimal characters', () => {
35+
const invalidHex = validHex64.slice(0, -1) + 'G'; // Replace last char with 'G' (invalid hex)
36+
expect(() => normalizePrivKey(invalidHex)).toThrow('Invalid private key: contains non-hexadecimal characters.');
37+
});
38+
39+
it('should throw an error if the key length is incorrect', () => {
40+
const shortKey = validHex64.slice(0, 62);
41+
expect(() => normalizePrivKey(shortKey)).toThrow('Invalid private key length');
42+
43+
const longKey = validHex64 + '12';
44+
expect(() => normalizePrivKey(longKey)).toThrow('Invalid private key length');
45+
});
46+
});

0 commit comments

Comments
 (0)