# P2P 거래확인증 설계 (지급정지 방어용)

> **목적**: P2P 환전 거래에서 구매자(입금자)의 자금이 사기 자금으로 판명되어 판매자(출금자) 계좌가 **지급정지(계좌 동결)** 될 경우, 동결 해제를 위한 **위변조 불가 거래확인증**을 발급한다.
> **상태**: 설계안 (v0.1, 2026-06-22). 구현 미착수.
> **결론 요약**: NFT 방식은 배제 — 거래별 온체인 앵커는 이미 USDT 정산 tx(`p2p_settlements.tx_hash`)로 1:1 존재하며, 확인증은 이를 인용하면 충분하다.

---

## 1. 배경 및 법적 맥락

### 1.1 위험 시나리오

```
구매자(입금자) ── KRW 송금 ──▶ 판매자(출금자) 개인 계좌
   │                                    │
   │ (구매자 자금이 보이스피싱 피해금)      │
   ▼                                    ▼
피해자 신고 → 통신사기피해환급법에 따라    판매자 계좌 즉시 지급정지
   판매자 계좌가 입금 경로로 지목됨            (선의의 판매자도 동결 대상)
```

판매자는 USDT를 정당하게 매도하고 그 대가로 KRW를 받았을 뿐이지만, 구매자 자금의 출처가 사기였다면 **수취 계좌(판매자)** 가 동결된다. 동결 해제(이의제기/지급정지 해제, 채무부존재확인 등)를 위해 판매자는 **"이 입금은 정당한 USDT 매도의 대가였다"** 를 입증해야 한다.

### 1.2 확인증이 입증해야 하는 3가지

| 입증 대상 | 의미 |
|----------|------|
| **신원** | 거래 양 당사자가 KYC 인증된 실재 인물임 (익명·대포통장 아님) |
| **대가성** | 받은 KRW에 대응하는 실재 가치(USDT)가 외부 시세 기준으로 교환됨 |
| **동시성·일치** | 거래가 플랫폼에 당시 기록되었고, KRW 송금 시각/금액과 정합함 |

### 1.3 한계 (반드시 인지)

- 확인증은 **동결 해제를 보장하지 않는다.** 은행/수사기관/법원에 제출하는 **증거 자료**일 뿐이며, 실제 해제는 이의제기·수사 협조·소송 절차를 통해 이루어진다.
- 확인증의 신뢰도는 **플랫폼의 KYC/AML 무결성**에 비례한다. eKYC·CODEF 기록이 부실하면 확인증도 약하다.

---

## 2. 증거 데이터 매핑 (현 DB 기준)

확인증에 필요한 모든 데이터는 이미 운영 DB에 저장되어 있다. 별도 수집 불필요.

### 2.1 신원 증거 (KYC)

| 당사자 | 데이터 | 위치 |
|--------|--------|------|
| 구매자(입금자) | 실명 | `p2p_deposit_orders.buyer_name` |
| | 전화 | `p2p_deposit_orders.buyer_phone` |
| | 은행/계좌/예금주 | `p2p_deposit_orders.buyer_bank_code` / `buyer_account_number` / `buyer_account_holder` |
| | eKYC uid | `p2p_deposit_orders.kyc_uid` (Axim eKYC) |
| 판매자(출금자) | 예금주/계좌/은행 | `bank_accounts.account_holder` / `account_number` / `bank_code` (← `p2p_withdraw_orders.bank_account_id`) |

### 2.2 오프체인 증거 (KRW 송금 사실)

| 데이터 | 위치 | 비고 |
|--------|------|------|
| 실제 통장 입금자명 | `p2p_matches.confirmed_depositor_name` | CODEF 조회 결과 desc 매칭 |
| 은행 입금 확인 참조 | `p2p_matches.bank_transfer_ref` | CODEF 이체 확인 번호 |
| 입금 확인 시각 | `p2p_matches.bank_confirmed_at` | |
| CODEF 호출 로그 | `call_logs.call_id` / `result_code` / `started_at` / `completed_at` | 조회 사실의 감사 추적 |
| 이체 확인증 파일 | `p2p_matches.dispute_evidence_url` | 분쟁 시 첨부된 원본 캡처 |

### 2.3 온체인 증거 (USDT 정산)

| 데이터 | 위치 | 비고 |
|--------|------|------|
| 정산 TX 해시 | `p2p_settlements.tx_hash` (= `p2p_matches.settlement_tx_hash`) | **매칭 1건당 1개 (1:1)** |
| 정산 완료 시각 | `p2p_settlements.completed_at` | |
| 송신/수신 지갑 | `p2p_settlements.from_wallet_address_id` / `to_wallet_address_id` → `wallet_addresses.address` | MASTER(A)→MASTER(B) |
| 네트워크 | `p2p_settlements.network_id` → `blockchain_networks.name` | TRON/EVM 등 |
| USDT 금액 | `p2p_settlements.usdt_amount` | |
| 원장 귀속 | `ledger_entries` (reference_type=`P2P_SETTLEMENT`) | **당사자 잔액 귀속의 핵심** |

### 2.4 거래 메타 / 시세

| 데이터 | 위치 |
|--------|------|
| 매칭 코드 | `p2p_matches.match_code` |
| 매칭 시각 | `p2p_matches.created_at` |
| 적용 환율(고정) | `p2p_matches.exchange_rate` (거래 시작 시점 박제, 이후 불변) |
| KRW/USDT 금액 | `p2p_matches.krw_amount` / `usdt_amount` |

---

## 3. 확인증 구성 (필드 스펙)

확인증 1장 = **1개 매칭(`p2p_matches`)** 기준. (분할 매칭은 매칭별로 개별 발급 + 묶음 인덱스 제공)

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
              P2P 거래 확인증
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[발급 정보]
  확인증 번호 : CERT-{match_code}-{발급seq}
  발급 일시   : 2026-06-22 15:00:00 KST
  발급 기관   : (플랫폼/파트너명, 사업자번호)

[거래 개요]
  매칭 코드   : pm_abc123
  거래 일시   : 2026-06-22 14:30:00 KST
  거래 유형   : P2P 크로스사이트 USDT 매매

[당사자] ※ 개인정보는 마스킹 + 원본은 봉인 첨부
  매도인(출금자) : 이○희 / 국민 004-***-*****0  (KYC 인증)
  매수인(입금자) : 김○수 / 신한 088-***-*****9  (eKYC 인증, uid=...)

[금원 — 받은 KRW]
  입금 금액   : 300,000 KRW
  실제입금자명 : 김철수  (CODEF 통장 대조 일치)
  입금 확인   : 2026-06-22 14:30:00 / ref=CODEF-...
  CODEF 호출  : call_id=uuid-xxx / result=OK

[대가 — 인도한 USDT] ※ 외부 시세 기준 가치 증명
  USDT 금액   : 272.73 USDT
  적용 환율   : 1,100.50 KRW/USDT  (거래시점 박제)
  시세 출처   : 빗썸 USDT/KRW, 스냅샷 2026-06-22 14:29:xx
  환산 검증   : 272.73 × 1,100.50 = 300,159 KRW ≈ 수취 KRW

[온체인 정산 증거]
  네트워크    : TRON
  TX 해시     : 0xabc123...   (탐색기 링크)
  송신 지갑   : 0x...(A 파트너 MASTER)
  수신 지갑   : 0x...(B 파트너 MASTER)
  정산 시각   : 2026-06-22 14:31:00
  ※ 커스터디 구조 설명: 본 거래는 플랫폼 에스크로 정산 방식으로,
    당사자별 자산 귀속은 아래 원장 기록으로 확정됨.

[원장 귀속 — 당사자 연결]
  매도인 차변(USDT 인도) : ledger #... 
  매수인 대변(USDT 수령) : ledger #...
  수수료 : ...

[무결성]
  데이터 해시 : sha256(...)        ← 위변조 검증용
  플랫폼 서명 : (서버 개인키 서명)    ← 발급 진위 검증용
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

### 핵심 설계 원칙

1. **대가성은 외부 시세로 증명한다.** `exchange_rate`(적용 환율)와 더불어 **거래 시점 빗썸 시세 스냅샷**을 명시 — "받은 KRW = 외부 시세 기준 USDT 가치"라는 독립 검증 가능한 등식을 제시.
2. **온체인 tx의 MASTER→MASTER 한계는 원장으로 메운다.** tx 자체는 풀 지갑 간 이동이므로, `ledger_entries`의 당사자별 차/대변 기록을 함께 제시해 "이 정산이 이 매수인에게 귀속됨"을 연결.
3. **개인정보 최소 노출.** 확인증 본문은 마스킹, 원본 신원/계좌는 **봉인 별첨**으로 분리(은행/수사기관 제출용에만 전체 공개).
4. **위변조 방지 2중.** ① 데이터 해시(내용 무결성) ② 플랫폼 서버 서명(발급 진위). 사용자가 임의 수정 불가.

---

## 4. 발급 흐름

```
판매자(또는 파트너 운영자)
   │ "확인증 발급" 요청 (match_code 또는 동결 계좌 기준 거래 조회)
   ▼
CertificateService.issue(matchId)
   ├─ p2p_matches / p2p_settlements / p2p_deposit_orders / bank_accounts / call_logs / ledger_entries 조회
   ├─ 시세 스냅샷 확정 (price_snapshots 또는 거래시점 시세 기록 인용)
   ├─ 데이터 정합성 검증 (KRW ≈ USDT×rate, depositor_name 일치, tx 존재)
   ├─ 확인증 데이터 JSON 구성 → sha256 해시
   ├─ 서버 개인키로 서명
   └─ PDF 생성 + transaction_certificates 테이블 기록(감사 추적)
   ▼
판매자에게 PDF 교부 (다운로드 / 파트너 콘솔)
```

### 발급 주체 / 권한

- **파트너 콘솔(partner-api)**: 파트너가 자사 회원의 거래 확인증 발급.
- **관리자 콘솔(admin-api)**: 운영자가 동결 대응 지원 시 직접 발급.
- 발급 이력은 `transaction_certificates`(신규)에 기록 — 누가/언제/어떤 거래에 대해 발급했는지 감사 추적.

---

## 5. 약점 및 보강

| 약점 | 영향 | 보강 |
|------|------|------|
| 온체인 tx가 MASTER→MASTER (풀 지갑) | "내부 이동 아니냐" 반박 | 원장(`ledger_entries`) 당사자 귀속 + 커스터디 구조 설명문 동봉 |
| **INNER 정산(동일 파트너)은 온체인 tx 없음** | 온체인 앵커 부재 | 확인증에 "내부 장부 정산" 명시 + 원장 기록으로 대체 입증. **이 경우가 가장 약하므로 별도 양식** |
| 시세 출처 미박제 시 대가성 약화 | 가치 증명 불충분 | 거래 시점 빗썸 시세를 **거래 레코드에 스냅샷 저장**(현재 `exchange_rate`는 적용환율만 — 원천 시세 별도 보존 권장) |
| `confirmed_depositor_name` 불일치/누락 | KRW-거래 연결 약화 | CODEF 원본 캡처(`dispute_evidence_url`) 보존 정책 강화 |
| 사용자 위변조 | 증거능력 상실 | 해시 + 서버 서명 (4장 무결성) |

### 권장 DDL 보강 (선택)

```sql
-- 거래확인증 발급 이력
CREATE TABLE transaction_certificates (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  cert_no       VARCHAR(64) NOT NULL UNIQUE,
  match_id      BIGINT NOT NULL,           -- p2p_matches.id
  issued_by     VARCHAR(64),               -- admin/partner 식별
  issued_for    VARCHAR(20),               -- 발급 목적 (FREEZE_DEFENSE 등)
  data_hash     CHAR(64) NOT NULL,         -- sha256
  signature     TEXT NOT NULL,             -- 서버 서명
  price_source  VARCHAR(50),               -- 'BITHUMB'
  price_krw     DECIMAL(20,4),             -- 거래시점 시세 스냅샷
  pdf_path      VARCHAR(500),
  created_at    DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)
);
```

(시세 원천 스냅샷을 거래 레코드 자체에 남기려면 `p2p_matches`에 `price_source` / `price_snapshot_krw` 컬럼 추가 검토)

---

## 6. 구현 지침 (요약)

> Cowork는 설계/지침만 제공. 실제 구현은 IntelliJ(Spring Boot)에서 수행.

1. **엔티티/리포지토리**: `transaction_certificates` 엔티티 + `IXRepository`. (CLAUDE.md 어노테이션 규칙 준수)
2. **CertificateService** (core): `issue(matchId, purpose)` — 데이터 수집·검증·해시·서명·PDF.
   - 서명: Axim `XBaseAccessTokenHandler` 또는 별도 서버 키페어(권장: 검증용 공개키 공개).
   - PDF: 기존 문서 생성 스택 활용.
3. **API**:
   - partner-api: `POST /api/v1/p2p/certificates` (회원 거래 확인증 발급)
   - admin-api: `POST /admin/p2p/certificates` (운영 지원 발급)
   - 검증 엔드포인트: `GET /verify/{certNo}` (해시·서명 공개 검증)
4. **시세 스냅샷**: 거래 생성 시 빗썸 원천 시세를 거래 레코드에 보존하도록 P2P/TORQ 거래 생성 로직 보완.
5. **INNER 정산 양식 분기**: 온체인 tx 없는 경우 별도 문구/원장 중심 양식.

---

## 7. 결론

- 네가 NFT로 만들려던 **"거래별 깔끔한 온체인 앵커"는 이미 `p2p_settlements.tx_hash`로 1:1 존재**한다. 자작 토큰을 얹을 이유가 없다.
- 지급정지 방어의 실효 증거는 **블록체인 아티팩트가 아니라 [KYC 신원 + 외부시세 대가성 + 온체인/원장 정산 + 플랫폼 서명]을 한 장에 묶은 확인증**이다.
- 모든 데이터는 DB에 있고, 발급 로직만 신규. **시세 원천 스냅샷 보존**과 **INNER 정산 양식 분기**가 보강 1순위.
