> ## Documentation Index
> Fetch the complete documentation index at: https://x-preview-mintlify-d8d2882f.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 문제 해결

> Chat XDK 오류, 보안 키 백업 복구, 복호화 실패, 서명된 전송 페이로드 구성 등 일반적인 X Chat 암호화 문제를 진단합니다.

이 페이지는 **X Chat 암호화와 Chat XDK 특유의** 문제—키, 보안 키 백업, 복호화/검증, 그리고 암호화된 전송 페이로드 구성—를 다룹니다.

웹훅, OAuth, HTTP 상태 코드, 속도 제한에 대해서는 일반 [X API](/ko/x-api/introduction)와 [인증](/ko/fundamentals/authentication/overview) 문서를 사용하세요.

***

## 키와 보안 키 백업

### 잠금 해제 실패(잘못된 패스코드)

* 패스코드가 `setup`에 사용된 것과 일치하는지 확인하세요
* 시도 간에 대기하세요; realm은 잘못된 추측을 속도 제한하며 실패가 너무 많으면 복구를 잠글 수 있습니다

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    try:
        chat.unlock(passcode)
    except ValueError as e:
        print(e)  # may mention InvalidPin or guesses remaining
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    try {
      await chat.unlock(passcode);
    } catch (e) {
      console.error((e as Error).message);
    }
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    chat.unlock(passcode_bytes).await?;
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    if err := chat.Unlock(passcode, juiceboxConfigJSON); err != nil {
        log.Println(err)
    }
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    try { chat.Unlock(passcode, juiceboxConfigJson); }
    catch (Exception e) { Console.WriteLine(e.Message); }
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    try { chat.unlock(passcode, juiceboxConfigJson); }
    catch (Exception e) { System.out.println(e.getMessage()); }
    ```
  </Tab>
</Tabs>

### 키 또는 아이덴티티가 설정되지 않아 암호화 또는 복호화 실패

먼저 개인 키를 로드한 다음 **세션 아이덴티티**—사용자 ID와 X의 레코드에 있는 `public_key_version`—를 설정하세요. `encrypt_*` 및 `prepare_*` 메서드는 이를 사용해 서명합니다. 세션 아이덴티티(그리고 명시적 호출별 오버라이드) 없이 이들을 호출하는 것은 오류입니다.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    chat.unlock(passcode)  # or: chat.import_keys(blob)
    chat.set_identity(my_user_id, signing_key_version)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await chat.unlock(passcode);
    chat.setIdentity(myUserId, signingKeyVersion);
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    chat.import_keys(&blob)?;
    chat.set_identity(&my_user_id, &signing_key_version);
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    blob, _ := chatxdk.Base64ToBytes(privateKeysB64)
    _ = chat.ImportKeys(blob)
    _ = chat.SetIdentity(myUserID, signingKeyVersion)
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    chat.ImportKeys(blobBytes);
    chat.SetIdentity(myUserId, signingKeyVersion);
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    chat.importKeys(blobBytes);
    chat.setIdentity(myUserId, signingKeyVersion);
    ```
  </Tab>
</Tabs>

### 메시지에 대한 대화 키 누락

`Message encrypted with key version '…' but no matching key found`와 같은 오류는 해당 메시지의 `conversation_key_version`에 대한 **원시** 키가 없다는 뜻입니다.

1. `conversation_key_change_event`(실시간 이벤트) 또는 `meta.conversation_key_events`(히스토리)에서 `extract_conversation_keys`로 키 자료를 복호화하거나, `decrypt_events`에 해당 blob들을 포함시키세요—`set_cache_keys(true)`이 활성화되면 `decrypt_events`가 각 대화의 최신 검증된 키도 보존하므로 이후 `decrypt_event`와 `encrypt_*` 호출이 이를 생략할 수 있습니다
2. 해당 버전에 대한 대화 키가 추가되었고 여전히 참여자인지 확인하세요([시작하기](/ko/xchat/getting-started#4-set-up-conversation-keys) 참고)

### 상대방에게 공개 키가 없음

아직 온보딩을 마치지 않았을 수 있습니다. 그들이 등록한 후에 **API 레퍼런스 → Encryption keys**에서 `public_key`, `signing_public_key`, `identity_public_key_signature`, `public_key_version`을 로드하세요.

***

## 복호화 및 서명

### 복호화 실패

* 오래되거나 잘못된 **원시** 대화 키, 또는 잘못된 키 버전
* 불완전한 `encoded_event` 문자열
* 이벤트 타입이 복호화 가능한 콘텐츠로 취급할 수 있는 암호화된 메시지가 아님

### 서명이 검증되지 않음

검증은 기본적으로 **실패 시 거부(fail-closed)** 상태입니다(`reject_unverified = true`): SDK가 이미 검증되지 않은 서명 이벤트를 거부하므로, 여기서의 실패는 검사를 켜야 한다는 뜻이 아니라 검증 입력이 잘못되었다는 뜻입니다. 일반적인 원인:

* **발신자**에 대한 서명 키 항목이 누락되거나 불완전(Chat XDK가 요구하는 모든 필드—[Chat XDK](/ko/xchat/xchat-xdk) 레퍼런스 참고)
* 호출에 서명 키가 전달되지 않았고 `set_signing_keys`로 저장된 것도 없음
* 발신자가 버전을 순환함—공개 키를 다시 가져오세요
* 허용 최소값 아래의 키 버전은 결코 검증되지 않습니다

`set_reject_unverified` setter는 이 기본값에서 **옵트 아웃**하기 위해 존재합니다(`false`, 권장하지 않음). 이전에 비활성화했다면, 실패 시 거부 기본값을 복원하세요:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    chat.set_reject_unverified(True)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    chat.setRejectUnverified(true);
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    chat.set_reject_unverified(true);
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    chat.SetRejectUnverified(true)
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    chat.SetRejectUnverified(true);
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    chat.setRejectUnverified(true);
    ```
  </Tab>
</Tabs>

### 답장이 `reply_preview_validation: "Invalid"`를 가짐

복호화된 답장은 `reply_preview_validation`(`"Valid"` / `"Invalid"`; JavaScript는 `'valid'` / `'invalid'` 사용)을 가질 수 있습니다. `Invalid`는 메시지 내에 인용된 미리보기가 임베드된 서명된 원본 이벤트와 일치하지 않는다는 뜻입니다—인용을 신뢰할 수 없는 것으로 취급하고 검증된 원본에서만 인용된 콘텐츠를 렌더링하세요. 메시지 자체는 별도로 검증되며 여전히 진짜입니다; 잘못된 미리보기에 대해 예외가 발생하지 않습니다.

### 오래된 이벤트가 영구적으로 검증 실패

`signature missing or no matching signing key` 또는 **오래된** 이벤트에 대한 ECDSA 불일치와 같은 오류는 영구적입니다. 서명은 불변이며 이벤트 자체에서 서명된 페이로드를 재구성하여 검증되므로, 다른 바이트로 서명되었거나(혹은 결코 서명되지 않은) 이벤트는 이후 모든 로드에서 실패합니다—어떤 재시도, 키 새로 고침, API 호출도 이를 치유할 수 없습니다. 이러한 이벤트는 재시도 가능한 오류가 아니라 묘비(tombstone)로 취급하세요. 대화 키를 순환하면 그 시점부터 깨끗하고 검증 가능한 히스토리가 시작됩니다; 새 메시지는 영향을 받지 않습니다.

***

## 전송 페이로드 구성

이러한 실수는 X Chat 암호화에 특유합니다(일반 HTTP 오류가 아닙니다):

| 문제             | 해결                                                                                                                                 |
| :------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
| 잘못된 키 바이트      | API의 암호화된 키 문자열이 아니라 **원시** 대화 키 바이트를 Chat XDK에 전달하세요                                                                              |
| 잘못된 JSON 필드 이름 | `encrypted_content` → `encoded_message_create_event` 및 `encoded_event_signature` → `encoded_message_event_signature`로 매핑하세요        |
| 잘못된 메시지 ID     | 반환된 페이로드의 `message_id`를 전송하세요—SDK가 생성하여 서명된 이벤트에 포함시키므로 다른 값은 실패합니다. 재시도에는 동일한 암호화된 페이로드를 재사용하여 ID가 두 번 생성되지 않도록 하세요               |
| 버전 불일치         | 사용하는 키와 `conversation_key_version`을 정렬하세요; `set_identity`에 전달된 서명 키 버전을 공개 키 레코드와 정렬하세요                                            |
| 경로 ID 형식       | URL 경로는 여전히 하이픈이 있는 대화 ID(`:` → `-`)가 필요하지만, 서명에는 SDK가 어떤 형식이든 받아들입니다: `A:B`, `A-B`(순서 무관), 혹은 그저 수신자 사용자 ID—모두 동일한 서명 바이트로 정규화됩니다 |

### 상태 변경 호출에 API가 400 반환

모든 상태 변경 채팅 호출—대화 키 추가 또는 순환, 그룹 생성, 멤버 추가—은 요청 본문에 \*\*`action_signatures`\*\*가 필요하며 API 경계에서 검증됩니다. 누락되거나 잘못된 형식의 항목(각각은 `message_id`, `encoded_message_event_detail`, 그리고 `signature`, `public_key_version`, `signature_version`을 가진 `message_event_signature`가 필요)은 즉시 HTTP 400 problem-details 응답을 반환합니다. SDK prepare 메서드(`prepare_conversation_key_change`, `prepare_group_create`, `prepare_group_members_change`)를 사용하고 반환된 **모든** 서명을 보내세요—그룹 생성과 멤버 추가는 두 개를 반환합니다.

***

## 미디어 암호화 및 복호화

* 첨부 파일을 참조하는 메시지와 **동일한** 대화 키(및 버전)를 사용하세요
* 다운로드 응답을 `decrypt_stream`을 실행할 때까지 **암호문**으로 취급하세요
* MIME 타입은 복호화 **후**에 추론하세요; 다운로드 `Content-Type`은 종종 실제 이미지 타입이 아닙니다

세부 사항: [미디어](/ko/xchat/media).

***

## 안전한 디버깅

암호화 실패를 조사할 때:

* 대화 ID, 이벤트 ID, 그리고 키 **버전**만 로깅하세요
* 평문, 패스코드, 개인 키, 또는 전체 키 blob은 로깅하지 **마세요**
* `set_identity`에 전달된 서명 키 버전이 공개 키 레코드의 `public_key_version`과 일치하는지 확인하세요
* 불완전한 히스토리의 경우, 복호화 전에 키 변경 메타데이터가 건너뛰어지지 않도록 **모든** 이벤트 페이지를 페이지 조회하세요
