stablyai/orca · critical · Error

Invalid public key: expected 32 bytes, got ${key.length} fro

Error message

Invalid public key: expected 32 bytes, got ${key.length} from "${b64.slice(0, 20)}..."

What it means

publicKeyFromBase64 decoded a base64 string whose byte length is not exactly 32 — the Curve25519 public key size required by tweetnacl's box.keyPair / box.before. The function base64-decodes via atob, builds a Uint8Array, and checks length. A wrong length means the pinned desktop public key is corrupt, truncated, or encoded with the wrong scheme.

Source

Thrown at mobile/src/transport/e2ee.ts:54

  for (let i = 0; i < bytes.length; i++) {
    binary += String.fromCharCode(bytes[i]!)
  }
  return btoa(binary)
}

function base64ToUint8(b64: string): Uint8Array {
  const binary = atob(b64)
  const bytes = new Uint8Array(binary.length)
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i)
  }
  return bytes
}

export function publicKeyFromBase64(b64: string): Uint8Array {
  const key = base64ToUint8(b64)
  if (key.length !== 32) {
    throw new Error(
      `Invalid public key: expected 32 bytes, got ${key.length} from "${b64.slice(0, 20)}..."`
    )
  }
  return key
}

export function publicKeyToBase64(key: Uint8Array): string {
  return uint8ToBase64(key)
}

export function encrypt(plaintext: string, sharedKey: Uint8Array): string {
  const messageBytes = u8(new TextEncoder().encode(plaintext))
  return uint8ToBase64(encryptBytes(messageBytes, sharedKey))
}

export function decrypt(encrypted: string, sharedKey: Uint8Array): string | null {
  const bundle = base64ToUint8(encrypted)
  const plaintext = decryptBytes(bundle, sharedKey)

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Trim whitespace and strip any data: prefix from the base64 string before decoding.
  2. Re-pair the host to get a fresh 32-byte (44 base64 chars) public key.
  3. Validate the base64 length before calling: 44 chars without padding = 32 bytes.
  4. If migrating from hex, convert hex→bytes first (64 hex chars = 32 bytes), then re-encode as base64.

Example fix

// before
export function publicKeyFromBase64(b64: string): Uint8Array {
  const key = base64ToUint8(b64)
  if (key.length !== 32) {
    throw new Error(`Invalid public key: expected 32 bytes, got ${key.length} ...`)
  }
  return key
}

// after — sanitize + validate before decode
export function publicKeyFromBase64(b64: string): Uint8Array {
  const clean = b64.trim().replace(/^data:.*?;base64,/, '')
  const key = base64ToUint8(clean)
  if (key.length !== 32) {
    throw new Error(`Invalid public key: expected 32 bytes, got ${key.length}`)
  }
  return key
}
Defensive patterns

Strategy: validation

Validate before calling

function isValidPublicKeyB64(b64: string): boolean {
  try {
    const clean = b64.trim().replace(/\s/g, '')
    const bytes = Uint8Array.from(atob(clean), (c) => c.charCodeAt(0))
    return bytes.length === 32
  } catch {
    return false
  }
}

// Use before calling connect()
if (!isValidPublicKeyB64(serverPublicKeyB64)) {
  throw new Error('Pairing key is corrupt — re-pair the host')
}

Type guard

function isBase64Key32(b64: string): boolean {
  try {
    const bytes = Uint8Array.from(atob(b64.trim()), (c) => c.charCodeAt(0))
    return bytes.length === 32
  } catch {
    return false
  }
}

Try / catch

try {
  const client = connect(endpoint, deviceToken, serverPublicKeyB64, options)
} catch (e) {
  if (e.message.startsWith('Invalid public key')) {
    triggerRePair()
  }
}

Prevention

When it happens

Trigger: The publicKeyB64 from pairing was truncated or padded wrong; a hex-encoded key was passed where base64 was expected; the key string has whitespace/newlines that atob mishandled; the stored host profile's publicKeyB64 was corrupted in AsyncStorage.

Common situations: Pairing QR/deeplink delivered a partial key; key copied with a stray newline or trailing '='; migration from a hex-key format; AsyncStorage corruption after an app crash mid-write; a relay key (16-byte) was passed instead of a Curve25519 key.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/63dd6fb6a5e61599. Report an issue: GitHub.