fix(web): fix two-stage private key input validation to support 0x prefix (#917)

## Problem
Users entering private keys with "0x" prefix failed validation incorrectly:
**Scenario:**
- User inputs: `0x1234...` (34 characters including "0x")
- Expected part1 length: 32 characters
- **Bug**: Code checks `part1.length < 32` → `34 < 32` →  FALSE → "Key too long" error
- **Actual**: Should normalize to `1234...` (32 chars) →  Valid
**Impact:**
- Users cannot paste keys from wallets (most include "0x")
- Confusing UX - valid keys rejected
- Forces manual "0x" removal
## Root Cause
**File**: `web/src/components/TwoStageKeyModal.tsx`
**Lines 77-84** (handleStage1Next):
```typescript
//  Bug: Checks length before normalizing
if (part1.length < expectedPart1Length) {
  // Fails for "0x..." inputs
}
```
**Lines 132-143** (handleStage2Complete):
```typescript
//  Bug: Same issue
if (part2.length < expectedPart2Length) {
  // Fails for "0x..." inputs
}
//  Bug: Concatenates without normalizing part1
const fullKey = part1 + part2 // May have double "0x"
```
## Solution
### Fix 1: Normalize before validation
**Lines 77-79**:
```typescript
//  Normalize first, then validate
const normalized1 = part1.startsWith('0x') ? part1.slice(2) : part1
if (normalized1.length < expectedPart1Length) {
  // Now correctly handles both "0x..." and "1234..."
}
```
**Lines 134-136**:
```typescript
//  Same for part2
const normalized2 = part2.startsWith('0x') ? part2.slice(2) : part2
if (normalized2.length < expectedPart2Length) {
  // ...
}
```
### Fix 2: Normalize before concatenation
**Lines 145-147**:
```typescript
//  Remove "0x" from both parts before concatenating
const normalized1 = part1.startsWith('0x') ? part1.slice(2) : part1
const fullKey = normalized1 + normalized2
// Result: Always 64 characters without "0x"
```
## Testing
**Manual Test Cases:**
| Input Type | Part 1 | Part 2 | Before | After |
|------------|--------|--------|--------|-------|
| **No prefix** | `1234...` (32) | `5678...` (32) |  Pass |  Pass |
| **With prefix** | `0x1234...` (34) | `0x5678...` (34) |  Fail |  Pass |
| **Mixed** | `0x1234...` (34) | `5678...` (32) |  Fail |  Pass |
| **Both prefixed** | `0x1234...` (34) | `0x5678...` (34) |  Fail |  Pass |
**Validation consistency:**
- Before: `validatePrivateKeyFormat` normalizes, but input checks don't 
- After: Both normalize the same way 
## Impact
-  Users can paste keys directly from wallets
-  Supports both `0x1234...` and `1234...` formats
-  Consistent with `validatePrivateKeyFormat` logic
-  Better UX - no manual "0x" removal needed
**Files changed**: 1 frontend file
- web/src/components/TwoStageKeyModal.tsx (+6 lines, -2 lines)
Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
0xYYBB | ZYY | Bobo
2025-11-12 09:52:14 +08:00
committed by GitHub
parent 1d1b31f1f1
commit 1e0da2ee39
+9 -3
View File
@@ -74,7 +74,9 @@ export function TwoStageKeyModal({
}, [isOpen, stage])
const handleStage1Next = async () => {
if (part1.length < expectedPart1Length) {
// ✅ Normalize input (remove possible 0x prefix) before validating length
const normalized1 = part1.startsWith('0x') ? part1.slice(2) : part1
if (normalized1.length < expectedPart1Length) {
setError(
t('errors.privatekeyIncomplete', language, {
expected: expectedPart1Length,
@@ -129,7 +131,9 @@ export function TwoStageKeyModal({
}
const handleStage2Complete = () => {
if (part2.length < expectedPart2Length) {
// ✅ Normalize input (remove possible 0x prefix) before validating length
const normalized2 = part2.startsWith('0x') ? part2.slice(2) : part2
if (normalized2.length < expectedPart2Length) {
setError(
t('errors.privatekeyIncomplete', language, {
expected: expectedPart2Length,
@@ -138,7 +142,9 @@ export function TwoStageKeyModal({
return
}
const fullKey = part1 + part2
// ✅ Concatenate after removing 0x prefix from both parts
const normalized1 = part1.startsWith('0x') ? part1.slice(2) : part1
const fullKey = normalized1 + normalized2
if (!validatePrivateKeyFormat(fullKey, expectedLength)) {
setError(t('errors.privatekeyInvalidFormat', language))
return