수명주기와 운영
호패는 한 번 찍고 끝나는 도장이 아니라 상태입니다. 발급 이후에도 계속 갱신되고, 자격을 잃으면 꺼집니다. 이 페이지는 그 상태가 어떻게 움직이고 누가 움직이는지를 다룹니다.
상태 기계
issue()
none ─────────────────▶ active ──── 만료 시각 경과 ────▶ lapsed
▲ │ │ │
│ │ └──── revoke(reasonHash) ───▶ revoked
│ │ │
└────────────────────┴──── issue() 재발급 (구 UID supersede) ◀┘
isVerified() none: false active: level ≥ minLevel lapsed: false revoked: false| 상태 | isVerified() | 진입 조건 | 빠져나오는 법 |
|---|---|---|---|
| active | ✅ 등급 충족 시 true | 발급 완료, 폐기·만료되지 않음 | 폐기 또는 만료 |
| lapsed | ❌ false | 만료 시각 경과 (현재 발급 TTL 365일). 트랜잭션 없이 자동 | 재검증 후 갱신 발급 |
| revoked | ❌ false | 제재 히트·재심사 탈락·사용자 요청 등. 사유 해시가 온체인에 기록됨 | 사유에 따라 재발급 가능 여부가 다름 |
| none | ❌ false | 해당 (지갑, 종류)에 어테스테이션이 없음 | 발급 |
판정은 fail-closed입니다. 상태를 알 수 없으면 false입니다. dApp 입장에서 네 상태의 차이는 로그와 사용자 안내에만 필요하고, 게이트 코드에는 필요 없습니다.
재발급과 supersede
같은 지갑에 다시 발급하면 살아있는 구 어테스테이션을 EAS에서 먼저 폐기한 뒤 새로 발급합니다. 매핑만 덮어쓰면 구 UID가 EAS에 유효한 채로 남아, 레지스트리를 거치지 않고 EAS를 직접 읽는 검증자에게는 여전히 “검증된 지갑”으로 보입니다. 그 경로를 막기 위한 동작입니다.
- ·재발급마다
Superseded(subject, kind, oldUid, newUid)이벤트가 남습니다. - ·상위 등급 발급도 같은 경로입니다. L2를 받으면 L1 어테스테이션은 교체됩니다.
- ·매핑을 벗어난 UID는
revokeByUid로 직접 폐기할 수 있습니다.
발급 전 — 심사 파이프라인
[지갑 바인딩] 소유권 서명 검증 — 주소·등급·claimsRoot·시각을 원문에 묶고 10분 유효
↓
[벤더 검증] 등급별 위임: 휴대폰 본인확인 → eKYC → 1원 계좌인증 (로드맵)
↓
[심사 엔진] 제재·워치리스트 대사 + 동일인 인덱스 검사 + 벤더 판정 검증
↓
[증적 생성] 벤더 참조·스크리닝 결과 ID·시각 → keccak256 → evidenceHash
↓
[발급 tx] issue(subject, kind, level, evidenceHash, claimsRoot, auditRef, expiration)지금 라이브인 구간은 소유권 서명 검증, 제재 명단·지정 지갑 대사, 온체인 노출 검사, 위험평가, 증적 생성, 발급 트랜잭션입니다. 없는 것은 벤더 신원 검증이고, 그래서 발급되는 등급이 L1 본인 선언에 묶여 있습니다.
발급 후 — 지속 모니터링
발급 시점에 깨끗했던 지갑이 계속 깨끗하다는 보장은 없습니다. 제재 명단은 매일 갱신되고, 벤더 쪽 검증 상태도 바뀝니다. 그래서 모니터링 루프가 제품의 핵심입니다.
매일 ① 제재 리스트 델타 수신·파싱
② 활성 어테스테이션 로스터와 대사 (사람 축 + 지갑 주소 축)
③ 히트 → 케이스 생성 → 판정 → 폐기 tx(사유 해시)
④ 마지막 동기화 시각을 헬스 지표로 노출케이스 처리와 통제
이름 기반 스크리닝은 오탐이 일상입니다. 동명이인 하나로 정상 사용자의 인증마크가 꺼지면 생태계 전체에서 차단되므로, 폐기와 방치 사이에 단계가 필요합니다.
| 통제 | 상태 | 내용 |
|---|---|---|
| SUSPENDED 상태 | 로드맵 | 조사 중에는 정지(isVerified false), 오탐으로 판명되면 어테스테이션을 소각하지 않고 복원합니다. 컨트랙트 v2 항목입니다. |
| 케이스 워크플로 | 로드맵 | 히트 → 케이스 오픈 → 심사역 배정 → 판정 → 근거 기록. 원클릭 폐기를 케이스 경유로 바꿉니다. |
| 메이커-체커 | 로드맵 | 폐기와 강제 재발급은 기안자와 승인자를 분리하고, 운영자 행위 자체를 감사 로그에 남깁니다. |
| 이의제기·구제 | 로드맵 | 잘못 폐기된 사용자의 재심 청구 채널과 처리 기한. |
폐기 사유와 SLA
사유 코드
온체인에 남는 것은 사유 해시 하나지만, 그 원본 레코드는 사유 코드를 갖습니다. 사유에 따라 재발급 가능 여부와 대기 기간이 달라지기 때문입니다.
| 코드 | 의미 | 재발급 |
|---|---|---|
| SANCTIONS_HIT | 제재·워치리스트 명단 일치 | 해소 확인 전까지 불가 |
| RESCREEN_FAIL | 재심사에서 요건 미달 | 재검증 후 가능 |
| USER_REQUEST | 사용자의 철회 요청 | 즉시 가능 |
| KEY_COMPROMISE | 지갑 키 유출 신고 | 새 지갑으로 발급 |
| VENDOR_REVOKED | 벤더 측 검증 취소 통보 | 벤더 재검증 후 |
사유 코드 표준화와 원본 레코드 스키마는 설계 단계입니다 로드맵. 현재 데모의 폐기는 제재 히트 시나리오 하나만 씁니다.
폐기 SLA
제품 지표로 공개하는 숫자입니다. 정의를 명확히 해두지 않으면 측정이 의미를 잃습니다.
| 구간 | 기점 | 종점 | 상태 |
|---|---|---|---|
| 데모 측정 | 운영자의 폐기 승인 (소유권 서명 완료) | 폐기 트랜잭션 확정 | 라이브 |
| 감사 대응 측정 | 제재 리스트 발행 시각 또는 벤더 웹훅 수신 시각 | 폐기 트랜잭션 블록 타임스탬프 | 로드맵 |
재이행 주기
어테스테이션에는 만료가 있습니다. 지금은 전 건 365일 고정이고, 위험등급에 따라 주기를 달리하는 것이 다음 단계입니다.
| 트리거 | 동작 |
|---|---|
| 만료 도래 | D-45 / D-7 알림 → 미재검증 시 자동 lapsed (별도 트랜잭션 없음) |
| 일일 재대사 히트 | 케이스 생성 → 판정 → 폐기 트랜잭션 → 동일인 다른 지갑 재심사 |
| 위험등급 상향 | 재이행 주기 단축, 필요 시 상위 등급 증빙 요구 |
| 벤더 검증 취소 통보 | 즉시 폐기 검토 — SLA 측정 대상 |
증적 — 무엇이 남는가
발급·폐기 트랜잭션은 반드시 해시를 동반합니다. 해시만으로는 아무것도 증명하지 못하므로, 원본 레코드를 보존하는 것이 짝입니다.
schemaVer 증적 규격 버전
vendorId 예: pass-mno · ekyc-vendor · bank-1won
vendorTxRef 벤더 측 검증 참조 (개인정보 아님)
subjectRef 동일인 인덱스용 솔티드 해시 (원본은 오프체인 암호화 보관)
screeningRef 제재 스크리닝 결과 ID
verifiedAt 검증 시각
evidenceHash = keccak256(abi.encode(위 필드들))- ·개인정보는 pre-image에도 원문으로 넣지 않습니다. 참조 ID와 해시만 들어갑니다.
- ·현재는 발급 응답으로 반환하고 시연분만 저장소에 보존합니다. 해시체인 저장소와 공개 검증기는 로드맵 입니다.
dApp 쪽에서 신경 쓸 것
- ·판정을 캐시하지 마세요. 폐기는 언제든 일어납니다. 가스가 들지 않는 read이니 트랜잭션 직전에 조회하는 것이 맞습니다.
- ·서버에 캐시해야 한다면 폐기 이벤트를 구독하세요. 웹훅 푸시는 로드맵 이라, 현재는 레지스트리 이벤트를 직접 듣는 방법뿐입니다.
- ·실패를 사용자에게 설명하세요. 같은 false여도 미발급·만료·폐기는 사용자 입장에서 할 일이 다릅니다.
getStatus()가 그 구분을 돌려줍니다.