> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Crypto — AT Protocol 암호화

> Laravel Bluesky 패키지의 Crypto 모듈. P-256 / secp256k1의 키 페어, DID 키, JWT, DPoP, 서명 변환을 설명합니다.

<Warning>
  Crypto는 고급 내부 구현입니다. 일반적인 게시·피드 취득·알림 등에는 필요 없습니다. AT Protocol의 저수준 서명 검증이나 OAuth 구현을 구축하는 경우에 참조하세요.
</Warning>

## AT Protocol에서의 암호화 개요

AT Protocol은 분산형 소셜 네트워크를 실현하기 위해 타원 곡선 암호(ECC)를 전면적으로 채택하고 있습니다. 주된 용도는 다음과 같습니다.

```mermaid theme={null}
graph TD
    A["키 페어<br>(P-256 / secp256k1)"] --> B["비밀 키로 서명"]
    A --> C["공개 키를 did:key로 인코드"]
    B --> D["DPoP 토큰(OAuth)"]
    B --> E["Commit 서명(저장소)"]
    B --> F["라벨 서명(Labeler)"]
    C --> G["DID Document에 등록"]
    G --> H["공개 키로 서명을 검증"]
```

| 곡선                | 알고리즘   | 주된 용도                          |
| ----------------- | ------ | ------------------------------ |
| secp256r1 (P-256) | ES256  | OAuth(DPoP, Client Assertion)  |
| secp256k1 (K256)  | ES256K | Feed Generator, Labeler의 서명 검증 |

***

## 키 페어 클래스

### AbstractKeypair

`AbstractKeypair`는 P256 / K256의 공통 기반 클래스입니다. 내부에서는 [phpseclib3](https://phpseclib.com/)를 사용합니다.

```php theme={null}
// 새로운 키 페어를 생성
$keypair = P256::create();
$keypair = K256::create();

// URL-safe Base64 인코딩의 비밀 키에서 로드
$keypair = P256::load($base64PrivateKey);

// PEM 형식으로 취득
$privatePem = $keypair->privatePEM();
$publicPem  = $keypair->publicPEM();

// JWK(JSON Web Key)로 변환
$jwk = $keypair->toJWK();
```

### P256 (secp256r1)

```php theme={null}
use Revolution\Bluesky\Crypto\P256;

$keypair = P256::create();
```

OAuth(DPoP / Client Assertion)에서 사용됩니다. `OAuthKey`는 P256을 상속하며, `config('bluesky.oauth.private_key')`에서 비밀 키를 로드합니다.

새로운 OAuth 비밀 키를 생성하는 Artisan 명령어도 준비되어 있습니다.

```bash theme={null}
php artisan bluesky:new-private-key
```

### K256 (secp256k1)

```php theme={null}
use Revolution\Bluesky\Crypto\K256;

$keypair = K256::create();
```

Feed Generator나 Labeler의 인증에서 사용됩니다. Bluesky의 PDS / Relay가 이 곡선으로 서명을 검증합니다.

***

## DidKey

`DidKey` 클래스는 공개 키를 `did:key` 형식으로 인코드·디코드합니다.

### did:key 형식이란

AT Protocol에서는 공개 키를 `did:key:z...`라는 문자열로 표현합니다. 이는 공개 키에 곡선 식별자를 부가하여 Base58btc로 멀티베이스 인코드한 것입니다.

```mermaid theme={null}
graph LR
    A["공개 키(PEM)"] --> B["곡선 식별자 + 압축 공개 키"]
    B --> C["Base58btc 인코드"]
    C --> D["did:key:z..."]
```

### 공개 키를 did:key로 인코드

```php theme={null}
use Revolution\Bluesky\Crypto\DidKey;
use Revolution\Bluesky\Crypto\K256;

$keypair = K256::create();
$publicPem = $keypair->publicPEM();

// did:key 형식으로 변환
$didKey = DidKey::format($publicPem);
// => "did:key:zQ3s..."
```

### DID Document에서 공개 키를 파싱

```php theme={null}
use Revolution\Bluesky\Crypto\DidKey;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Support\DidDocument;

$didDoc = DidDocument::make(
    Bluesky::identity()->resolveDID('did:plc:***')->json()
);

// DID Document의 publicKey(multibase)에서 phpseclib3의 공개 키 객체를 생성
$publicKey = DidKey::parse($didDoc->publicKey());
```

***

## JsonWebToken (JWT)

`JsonWebToken` 클래스는 JWT의 인코드·디코드를 제공합니다.

```php theme={null}
use Revolution\Bluesky\Crypto\JsonWebToken;

// JWT를 생성(비밀 키 PEM으로 서명)
$token = JsonWebToken::encode(
    payload: ['iss' => 'did:plc:***', 'aud' => 'https://bsky.social', 'exp' => time() + 60],
    key: $privatePem,
    alg: 'ES256',
);

// JWT를 디코드(서명 검증 없음)
$payload = JsonWebToken::decode($token);
```

***

## DPoP (Demonstrated Proof of Possession)

DPoP는 OAuth 액세스 토큰을 특정 클라이언트 키 페어에 결부시키는 보안 메커니즘입니다. 토큰의 리플레이 공격을 방지합니다.

```mermaid theme={null}
sequenceDiagram
    participant App as Laravel 앱
    participant AS as Authorization Server
    participant RS as Resource Server

    App->>AS: PAR 요청(+ DPoP 헤더)
    AS-->>App: request_uri
    App->>AS: 토큰 요청(+ DPoP 헤더)
    AS-->>App: 액세스 토큰(DPoP bound)
    App->>RS: API 요청(+ DPoP 헤더 + ath)
    RS-->>App: 응답
```

`DPoP` 클래스는 내부에서 사용되며, 일반적으로 직접 호출할 필요가 없습니다. `OAuthAgent` 미들웨어가 자동으로 DPoP 헤더를 부여합니다.

```php theme={null}
// DPoP 프루프 생성(OAuth 토큰 요청용)
$proof = DPoP::authProof(
    jwk: $jsonWebKey,
    url: 'https://bsky.social/oauth/token',
    method: 'POST',
    nonce: $nonce,
);

// DPoP 프루프 생성(API 요청용, ath 포함)
$proof = DPoP::apiProof(
    jwk: $jsonWebKey,
    url: 'https://api.bsky.app/xrpc/...',
    method: 'GET',
    token: $accessToken,
    nonce: $nonce,
);
```

***

## Signature (서명 포맷 변환)

AT Protocol은 64바이트의 컴팩트 서명 형식을 사용하지만, phpseclib3은 ASN.1 DER 형식을 반환합니다. `Signature` 클래스는 이 변환을 담당합니다.

```php theme={null}
use Revolution\Bluesky\Crypto\Signature;

// ASN.1 DER → 컴팩트(64바이트)
$compact = Signature::toCompact($derSignature);

// 컴팩트 → ASN.1 DER
$der = Signature::fromCompact($compactSignature);
```

***

## JsonWebKey

`JsonWebKey`는 JWK(JSON Web Key)를 나타내는 클래스입니다. DPoP 프루프 생성에 사용됩니다.

```php theme={null}
use Revolution\Bluesky\Crypto\JsonWebKey;

// 키 페어에서 JWK를 생성
$jwk = $keypair->toJWK();

// 또는 직접 인스턴스화
$jwk = JsonWebKey::load(['kty' => 'EC', 'crv' => 'P-256', ...]);
```

***

## OAuthKey

`OAuthKey`는 P256을 상속한 OAuth 전용 키 클래스이며, `config('bluesky.oauth.private_key')` 값을 비밀 키로 사용합니다.

```php theme={null}
use Revolution\Bluesky\Crypto\OAuthKey;

// 설정에서 비밀 키를 로드
$oauthKey = OAuthKey::load();

// Client Assertion JWT의 서명에 사용됨
$jwk = $oauthKey->toJWK();
```

일반적으로 Bluesky Socialite 내부에서 자동으로 처리됩니다.

***

## 참고 링크

* [AT Protocol: Identity](https://atproto.com/guides/identity)
* [AT Protocol: Cryptography spec](https://atproto.com/specs/cryptography)
* [phpseclib3](https://phpseclib.com/)

<Info>
  Source: [src/Crypto/](https://github.com/invokable/laravel-bluesky/tree/main/src/Crypto)
</Info>


## Related topics

- [암호화(Encryption)](/ko/encryption.md)
- [설정](/ko/configuration.md)
- [Redis](/ko/redis.md)
- [OAuth 2.0 인증 - Google Sheets API for Laravel](/ko/packages/laravel-google-sheets/oauth.md)
- [CSRF 보호](/ko/csrf.md)
