市場で最も安全な通信プロトコル（上編）- X3DHプロトコル解析

# X3DHプロトコル

この文書は、Signalのホワイトペーパーを翻訳したもので、個人的な解釈と最もシンプルなコード例を含んでいます。

https://signal.org/docs/specifications/x3dh/

## はじめに

本書では、「X3DH」（別名「拡張トリプルDiffie-Hellman」）キー合意プロトコルについて説明しています。X3DHは、両者が互いの公開鍵を用いて相互認証を行うことに基づき、共有された秘密鍵を確立する仕組みです。このプロトコルは、前方秘匿性および暗号学的な否認不可能性を提供します。

X3DHは特に、一方のユーザー（「Bob」）がオフラインであっても、あらかじめサーバーに特定の情報を公開しておく非同期環境に適した設計となっています。もう一方のユーザー（「Alice」）は、その情報を利用してBobに対して暗号化されたデータを送信し、将来の通信における共有鍵の確立を試みます。

## 準備

### X3DHパラメータ

アプリケーションがX3DHを使用する際、以下のような複数のパラメータを設定する必要があります：

| 名前 | 定義 |
| --- | --- |
| curve | X25519 または X448 |
| hash | 256または512ビットハッシュ関数 (例: SHA-256 または SHA-512) |
| info | アプリケーションを識別するためのハードコードされた情報 |

例えば、curveにX25519、hashにSHA-512、infoに"MyProtocol"を選択できます。

アプリケーションでは、X25519やX448の公開鍵*PK*をバイト列に変換するためのエンコード関数「Encode(PK)」を定める必要があります。推奨されるエンコーディング方式には、楕円曲線の種類を示すいくつかの単一バイト定数と、文献[[1]](https://coda.io/d/_dfQETo8p_D9/_suhAa#_luvMM)に定義されているように、*u*座標のリトルエンディアン形式のバイトエンコーディングが含まれています。

### 暗号学的記号

X3DHプロトコルでは、以下の記号が使用されます：

- X と Y のバイト列を直接連結したものを X||Y と表します。たとえば、X が "123" で、Y が "456" の場合、X||Y は "123456" を意味します。
- DH(PK1, PK2) は、公開鍵 PK1 と PK2 を使って計算される楕円曲線 Diffie-Hellman (ECDH) 関数からの出力であり、これにより生成される共有秘密鍵を示すバイト列です。楕円曲線 Diffie-Hellman 関数は、参考文献[1]で説明されている X25519 もしくは X448 関数を指し、使用する curve パラメータによって決定されます。
- Sig(PK, M) は、バイト列 M に対するXEdDSA署名を指し、公開鍵 PK に関連する秘密鍵を使用して M に対して行われた署名です。この署名は公開鍵 PK によって検証が可能なバイト列を生成します。XEdDSAの署名生成およびその検証方法は文献 [2] で定義されています。
- KDF(KM) はHKDFアルゴリズム[3]によって生成される32バイトの出力、及びその入力を指します。
- 「HKDF input key material　= F || KM」。ここで、KMはキーマテリアルを含む入力バイト列であり、Fはバイト列です。楕円曲線がX25519の場合は、Fには0xFFが32個含まれ、X448の場合は0xFFが57個含まれます。FはXEdDSA[2]の暗号化ドメイン分離のために使用されます。HKDFのsaltは、ハッシュ出力の長さに等しい長さのゼロ埋めされたバイト列です。HKDFのinfoは、2.1セクションに記述されている情報パラメータを指します。

### 役割

X3DHプロトコルでは、三つの役割が関わります。Alice、Bob、そしてServerです。

Aliceは暗号技術を利用してBobに初期データを送信し、双方向通信のための共有秘密鍵を確立したいと考えています。

Bobもまた、Aliceのような他者と共有秘密鍵を確立し、暗号化されたデータの送受信を行いたいと考えています。しかし、Aliceが接触を試みるとき、Bobはオフラインの可能性があります。これを解決するために、BobはServerと関係を構築します。

ServerはAliceからBobへのメッセージを一時保存し、Bobが後ほどそれらのメッセージを受け取れるようにすることができます。さらに、ServerはBobが公開するいくつかのデータを保管し、Aliceのような他者に提供する役割も担います。Serverへの信頼度については、[セクション4.7]で詳しく議論されています。

一部のシステムでは、Serverの役割が複数のエンティティに分担されていることがありますが、簡略化のために、ここではAliceとBobに上記の機能を提供する単一のServerのみが存在するとします。

### 鍵

X3DHプロトコルでは、次のような楕円曲線の公開鍵が使用されます。

| 名前 | 定義 |
| --- | --- |
| IKA | Aliceのアイデンティティキー |
| EKA | Aliceの一時鍵 |
| IKB | Bobのアイデンティティキー |
| SPKB | Bobの署名付き事前鍵 |
| OPKB | Bobのワンタイム事前鍵 |

公開鍵にはそれぞれ対応する秘密鍵が存在しますが、説明を簡略化するため、ここでは公開鍵に注目します。

X3DHプロトコルを実行する際に使用される公開鍵は、全てX25519形式であるか、または全てX448形式でなければなりません。これは、使用する曲線のパラメータ[1]によって決まります。

各当事者は、長期にわたるアイデンティティキーを持っています（AliceのはIKA、BobのはIKB）。

Bobには、定期的に更新される署名付き事前鍵（SPKB）と、X3DHプロトコルの実行のたびに使用されるワンタイム事名鍵（OPKB）のセットがあります（「事前鍵」という名称は、これらのキーがAliceがプロトコルを開始する前にBobがサーバーに公開するプロトコル情報であるために付けられています）。

## X3DHプロトコル

### 概要

X3DHは3つの段階から成ります。

1. Bobは、自身のアイデンティティキーと事前鍵をサーバーに公開します。
2. Aliceはサーバーから「事前鍵バンドル」を取得し、それを使ってBobに初期メッセージを送信します。
3. BobはAliceからの初期メッセージを受け取り、処理します。

以下のセクションでこれらの段階について説明します。

プロトコルを実行するたびに、Aliceは公開鍵EKAを持つ新しい一時鍵ペアを生成します。

プロトコルが成功に実行された後、AliceとBobは32バイトの共有秘密鍵SKを持つことになります。この鍵はX3DHの後に続くセキュアな通信プロトコルで使用することができますが、第4章に記載されたセキュリティに関する考慮事項に従う必要があります。

### 公開鍵の公開

Bobははサーバーに以下を含む一連の楕円曲線公開鍵を公開します：

- BobのアイデンティティキーIKB
- Bobの署名付き事前鍵SPKB
- Bobの事前鍵の署名 Sig(IKB, Encode(SPKB))
- 一連のBobのワンタイム事前鍵（OPKB1, OPKB2, OPKB3, ...）

Bobは自分のアイデンティティキーをサーバーに一度だけアップロードする必要があります。しかし、例えばサーバーからワンタイム事前鍵の在庫が減少している旨の通知を受けた際などには、Bobは新しいワンタイム事前鍵を追加でアップロードすることが可能です。

Bobは定められた時間間隔で（例えば、毎週または毎月）新しい署名付き事前鍵とその鍵の署名をサーバーにアップロードすることになります。新たにアップロードされた署名済み事前鍵とその署名は、以前のものを置き換える形となります。

新しい署名付き事前鍵をアップロードした後、Bobは一定の期間、以前の署名付き事前鍵に対応する秘密鍵を保持します。これにより、その事前鍵を使って送信されたメッセージが遅れて到着した場合でも処理できるようになります。最終的には、前方秘匿性を確保するために、その秘密鍵を削除する必要があります（ワンタイム事前鍵の秘密鍵は、関連するメッセージがBobに届いた時点で削除されることになります。詳細は第3.4節を参照してください）。

### 初期メッセージの送信

BobとのX3DH鍵交換プロトコルを行うために、、Aliceはサーバーに接続し、以下の値を含む「事前鍵バンドル」を取得します。

- BobのアイデンティティキーIKB
- Bobの署名付き事前鍵SPKB
- Bobの事前鍵の署名 Sig(IKB, Encode(SPKB))
- （非必要）Bobのワンタイム事前鍵OPKB

Bobのワンタイム事前鍵がサーバーに存在している場合は、サーバーはその中から一つをAliceに提供した後、そのプリキーを削除します。もしサーバー上にBobのワンタイム事前鍵が一つも残っていない場合は、事前鍵バンドルにワンタイム事前鍵は含まれなくなります。

Aliceは、事前鍵の署名を検証し、その検証に失敗した場合には鍵交換プロトコルを中止します。その検証が成功した後、アリスは自身の公開鍵EKAを用いて一時鍵ペアを生成します。

バンドルにワンタイム事前鍵が含まれていなければ、Aliceは以下の方法で計算を行います：

DH1 = DH(IKA, SPKB)    DH2 = DH(EKA, IKB)    DH3 = DH(EKA, SPKB)    SK = KDF(DH1 || DH2 || DH3)

もしバンドルにワンタイム事前鍵が含まれている場合、計算は追加のDHを取り入れるように変更されます：

DH4 = DH(EKA, OPKB)    SK = KDF(DH1 || DH2 || DH3 || DH4)

下記の図は、鍵間のDH計算を示しています。DH1とDH2は相互認証を提供し、一方でDH3とDH4は前方秘匿性を提供します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3688781/50cffdd3-7437-23f2-82e2-cc59c04ba2b8.png)

SKを計算した後、Aliceは自身の一時鍵とDHの出力を削除します。

次に、Aliceは両者の識別情報を含む「関連データ」のバイト列ADを計算します：

AD = Encode(IKA) || Encode(IKB)

Aliceは、ADに自分とBobのユーザー名、証明書、その他の識別情報など、追加情報を付加することもできます。

その後、AliceはBobに初期メッセージを送信します。これには以下の内容が含まれます：

- Aliceのアイデンティティキー IKA
- Aliceの一時鍵 EKA
- AliceがBobのどの事前鍵を使用したかを示す識別子
- AEAD暗号スキーム[4]を用いて暗号化された初期の暗号文、関連データとしてのADを使用し、そして暗号キーとしてSK、あるいはSKから導出された何らかの暗号的PRFの出力を使用します。

初期暗号文は通常、X3DHに続く通信プロトコルの最初のメッセージとして用いられます。つまり、この暗号文は、X3DHプロトコルに続く初めての情報を含むものであり、同時にAliceのX3DHにおける初期メッセージの一部を構成します。

送信後、AliceはSK自体またはSKから派生した鍵を使用して、X3DH後のプロトコルでBobとの通信を続けることができが、下記セキュリティ上の考慮事項を従っています。

### 初期メッセージの受信

Aliceからの初期メッセージを受け取った後、BobはメッセージからAliceのアイデンティティ鍵と一時鍵を取り出します。Bobは自身のアイデンティティ秘密鍵と、Aliceが使用した任意の署名された事前鍵やワンタイム事前鍵（もしあれば）に対応する秘密鍵もロードします。

これらの鍵を使用して、Bobは前のセクションで説明されたDHとKDFの計算を繰り返し、SKを導出した後、DH値を削除します。

次に、BobはIKAとIKBを使用して、前のセクションで述べたようにADバイト列を構築します。最後に、BobはSKとADを使用して初期暗号文を復号化しようとします。初期暗号文の復号化に失敗した場合、Bobはプロトコルを中止し、SKを削除します。

初期暗号文の復号化に成功した場合、プロトコルはBobにとって完了したことになります。Bobは、前方秘匿性を実現するために使用された任意のワンタイム事前鍵の秘密鍵を削除します。その後、Bobは[セクション4]のセキュリティ上の考慮事項に従いながら、X3DHの後のプロトコルでSKまたはSKから派生した鍵を使用してAliceと通信を続けることができます。

## セキュリティ上の考慮事項

### 認証

X3DHキー協定の前または後に、双方は自身のアイデンティティ公開鍵IKAとIKBを何らかの認証チャネルを通じて比較することができます。例えば、公開鍵のフィンガープリントを手動で比較したり、QRコードをスキャンする方法があります。この方法については、本文書の範囲外です。

認証を行わない場合、当事者は自身が誰と通信しているのかについての暗号的な保証を得ることができません。

### プロトコルのリプレイ

Aliceの初期メッセージにワンタイム事前鍵が使用されていない場合、それはBobに対してリプレイされる可能性があり、Bobはそれを受け入れるでしょう。これにより、BobはAliceが同じメッセージ（またはメッセージ群）を繰り返し送信したと考える可能性があります。

この状況を軽減するために、X3DHの後のプロトコルでは、Bobの新しいランダムな入力に基づいてAliceに対して迅速に新しい暗号化キーをネゴシエートすることが望ましいです。これは、Diffie-Hellmanベースのラチェットプロトコルの典型的な動作です[[5]]。

Bobは、観測されたメッセージのブラックリストを維持する、古い署名付き事前鍵をより迅速に置き換えるなど、他の緩和策を試みることができます。これらの緩和策の分析は、本文書の範囲を超えています。

### リプレイとキーの再利用

前のセクションで議論されたリプレイの別の結果として、成功したリプレイされた初期メッセージは、Bobが異なるプロトコルの実行で同じSKを導出することになります。

このため、X3DHの後のどのプロトコルも、Bobが暗号化データを送信する前に、暗号化キーをランダム化する必要があります。例えば、BobはDHベースのラチェットプロトコルを使用し、SKと新しく生成されたDHの出力を組み合わせて、ランダムな暗号化キーを得ることができます[[5]]。

Bobの暗号化キーをランダム化しない場合、災害を引き起こすキーの再利用が発生する可能性があります。

### 否認不可能性

X3DHは、AliceやBobが自分たちの通信内容や通信の事実を証明するための公開可能な暗号証明を提供しません。

OTRプロトコル[[6]]のように、特定の状況下で、AliceやBobの正当な秘密鍵が第三者に漏洩した場合、その第三者はAliceとBobの間の通信記録を得ることができ、その記録はAliceやBobの正当な秘密鍵にアクセスできる他の人物（すなわちAliceやBob自身、または彼らの秘密鍵を漏洩させた他の人物）によってのみ作成可能です。

プロトコルの実行中にどちらかの当事者が第三者と協力していた場合、彼らはそのような第三者に自分たちの通信の証拠を提供することができます。このような「オンライン」否認不可能性の制限は、非同期設定の固有の要素であると思われます[[7]]。

### 署名

互いに認証し、前方秘匿性を確保することがDH計算によって実現されており、事前鍵の署名を省略することが魅力的に思えるかもしれません。しかし、これは「弱い前方秘匿性」の攻撃を許すことになります。悪意のあるサーバーが偽の事前鍵を含む事前鍵バンドルをAliceに提供し、その後BobのIKBを侵害してSKを計算することができるようになります。

また、DHベースの相互認証（つまり、DH1とDH2）をアイデンティティ鍵の署名で置き換えることも魅力的に思えるかもしれません。しかし、これは否認可能性を減少させ、初期メッセージのサイズを増加させ、一時鍵や事前鍵の秘密鍵が侵害された場合や署名スキームが破られた場合の損害を増加させます。

### キーの漏洩

一方の秘密鍵が侵害されると、セキュリティに壊滅的な影響を与える可能性がありますが、一時鍵や事前鍵の使用によってある程度の緩和が提供されます。

一方のアイデンティティ秘密鍵が侵害されると、他者にその人になりすますことが可能になります。一方の事前鍵の秘密鍵が侵害されると、以前または以後のSK値のセキュリティに影響を与える可能性があり、これは多くの考慮事項に依存します。

すべての可能な侵害シナリオについての包括的な分析は、本文書の範囲を超えていますが、以下に信頼できるシナリオの一部の分析を示します。

プロトコルの実行中にワンタイム事前鍵が使用された場合、将来のある時点でBobのアイデンティティ鍵と事前鍵の秘密鍵が漏洩しても、OPKBの秘密鍵が削除されていれば、以前のSKが漏洩することはありません。

ワンタイム事前鍵がプロトコルの実行に使用されていない場合、そのプロトコルの実行中にIKBとSPKBの秘密鍵が漏洩すると、以前に計算されたSKが漏洩する可能性があります。署名された事前鍵を頻繁に交換することで、この問題を緩和できますし、X3DHの後のラチェットプロトコルのように、迅速に新しいキーにSKを置き換えて新たな前方秘匿性を提供することもできます[[5]]。

事前鍵の秘密鍵が侵害されると、攻撃が未来に拡大する可能性があります。たとえば、SK値の受動的計算や、侵害された側に他の任意の当事者をなりすますことができます（「キー侵害によるなりすまし」）。これらの攻撃は、侵害された側がサーバー上の侵害された事前鍵を置き換える（受動的攻撃の場合）、または侵害された署名された事前鍵の秘密鍵を削除する（キー侵害によるなりすましの場合）まで可能です。

### サーバーの信頼性

悪意のあるサーバーは、AliceとBobの間の通信を妨害する可能性があります（例えば、メッセージの配信を拒否することによって）。

AliceとBobが[セクション4.1]で述べたように互いに認証する場合、サーバーが利用できる追加の攻撃は、ワンタイム事前鍵の提供を拒否することにより、SKの前方秘匿性が署名された事前鍵の寿命に依存するようにすることだけです（前のセクションで分析されたように）。

一方が他方のワンタイム事前鍵を悪意を持って消費する場合にも、この初期の前方秘匿性の低下が発生する可能性があるため、サーバーはこのような状況を防ぐべきです。例えば、事前鍵バンドルの取得率に制限を設けることによります。

## 参考文献

[1] A. Langley, M. Hamburg, and S. Turner, “Elliptic Curves for Security.” Internet Engineering Task Force; RFC 7748 (Informational); IETF, Jan-2016. http://www.ietf.org/rfc/rfc7748.txt

[2] T. Perrin, “The XEdDSA and VXEdDSA Signature Schemes,” 2016. https://whispersystems.org/docs/specifications/xeddsa/

[3] H. Krawczyk and P. Eronen, “HMAC-based Extract-and-Expand Key Derivation Function (HKDF).” Internet Engineering Task Force; RFC 5869 (Informational); IETF, May-2010. http://www.ietf.org/rfc/rfc5869.txt

[4] P. Rogaway, “Authenticated-encryption with Associated-data,” in Proceedings of the 9th ACM Conference on Computer and Communications Security, 2002. http://web.cs.ucdavis.edu/~rogaway/papers/ad.pdf

[5] T. Perrin, “The Double Ratchet Algorithm (work in progress),” 2016.

[6] N. Borisov, I. Goldberg, and E. Brewer, “Off-the-record Communication, or, Why Not to Use PGP,” in Proceedings of the 2004 aCM workshop on privacy in the electronic society, 2004. http://doi.acm.org/10.1145/1029179.1029200

[7] N. Unger and I. Goldberg, “Deniable Key Exchanges for Secure Messaging,” in Proceedings of the 22Nd aCM sIGSAC conference on computer and communications security, 2015. http://doi.acm.org/10.1145/2810103.2813616

[8] C. Kudla and K. G. Paterson, “Modular Security Proofs for Key Agreement Protocols,” in Advances in Cryptology - ASIACRYPT 2005: 11th International Conference on the Theory and Application of Cryptology and Information Security, 2005. http://www.isg.rhul.ac.uk/~kp/ModularProofs.pdf

[9] S. Blake-Wilson, D. Johnson, and A. Menezes, “Key agreement protocols and their security analysis,” in Crytography and Coding: 6th IMA International Conference Cirencester, UK, December 17–19, 1997 Proceedings, 1997. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.25.387

[10] C. Cremers and M. Feltz, “One-round Strongly Secure Key Exchange with Perfect Forward Secrecy and Deniability.” Cryptology ePrint Archive, Report 2011/300, 2011. http://eprint.iacr.org/2011/300

[11] J. P. Degabriele, A. Lehmann, K. G. Paterson, N. P. Smart, and M. Strefler, “On the Joint Security of Encryption and Signature in EMV.” Cryptology ePrint Archive, Report 2011/615, 2011. http://eprint.iacr.org/2011/615

## 附録：X3DHのコードの簡単な実装

```
 // server
 server := NewServer()
 // alice
 alice := NewX3DHKeyPairs(curve.GenerateKeyPair())
 alice.ResetSignedPreKeyPair()
 server.AddPubKeys("alice", alice.GetPublicKeys())
 ```

 ```
  // bob
 bob := NewX3DHKeyPairs(curve.GenerateKeyPair())
 bob.ResetSignedPreKeyPair()
 server.AddPubKeys("bob", bob.GetPublicKeys())
```

```
 // alice send message to bob
 bobPublicKeys := server.GetPubKeys("bob")
 sharedKey := alice.BuildSharedKey(bobPublicKeys)
 sendKeyPackage := alice.BuildSendKeyPackage(bobPublicKeys, sk)
```

```
 sharedKey2 := bob.GetSharedKey(sendKeyPackage)
 t.Logf("equal: %v", bytes.Equal(sharedKey, sharedKey2))
 ```

上記のコードは、一度のコード交換の例を示しています。サーバーが公開鍵バンドルのみを保存していることがわかります。つまり、サーバーは各人のメッセージ本体を解析することができません。

さらに、送信者は公開鍵バンドルを一度だけ取得する必要があり、その後はそのサーバーとのやり取りは必要ありません。つまり、これら2人が最終的に通信を行ったかどうかについて、サーバーは知ることができません。

以下は、プロトコルに基づいて整理されたGo言語での実装です:

```
package main

type Server struct {
	pubKeys map[string]*X3DHPublicKeys
}

func NewServer() *Server {
	return &Server{
		pubKeys: make(map[string]*X3DHPublicKeys),
	}
}

func (s *Server) AddPubKeys(name string, pubKeys *X3DHPublicKeys) {
	s.pubKeys[name] = pubKeys
}

func (s *Server) GetPubKeys(name string) *X3DHPublicKeys {
	k, _ := s.pubKeys[name]
	return k
}
```
```
package main

import (
	"encoding/base64"
	"learnGo/learn/pprof/aes"
	"learnGo/learn/pprof/curve"
)

type X3DHKeyPairs struct {
	IdentityKeyPair   *curve.KeyPair
	EphemeralKeyPair  *curve.KeyPair
	SignedPreKeyPair  *curve.KeyPair
	OneTimePreKeyPair *curve.KeyPair
}

func NewX3DHKeyPairs(identityKey *curve.KeyPair) *X3DHKeyPairs {
	return &X3DHKeyPairs{
		IdentityKeyPair: identityKey,
	}
}

func (k *X3DHKeyPairs) ResetSignedPreKeyPair() {
	k.SignedPreKeyPair = curve.GenerateKeyPair()
}

type X3DHPublicKeys struct {
	IdentityKey       [32]byte
	SignedPreKey      [32]byte
	PreKeySignature   []byte
	OneTimePreKey     [32]byte
	haveOneTimePreKey bool
}

func (k *X3DHKeyPairs) GetPublicKeys() *X3DHPublicKeys {
	opk := [32]byte{}
	if k.OneTimePreKeyPair != nil {
		opk = k.OneTimePreKeyPair.PublicKey
	}
	return &X3DHPublicKeys{
		IdentityKey:       k.IdentityKeyPair.PublicKey,
		SignedPreKey:      k.SignedPreKeyPair.PublicKey,
		PreKeySignature:   []byte{},
		OneTimePreKey:     opk,
		haveOneTimePreKey: k.OneTimePreKeyPair != nil,
	}
}

func (k *X3DHKeyPairs) BuildSharedKey(peerPublicKeys *X3DHPublicKeys) []byte {
	k.EphemeralKeyPair = curve.GenerateKeyPair()

	dh1 := k.IdentityKeyPair.GenerateShareKey(peerPublicKeys.SignedPreKey)
	dh2 := k.EphemeralKeyPair.GenerateShareKey(peerPublicKeys.IdentityKey)
	dh3 := k.EphemeralKeyPair.GenerateShareKey(peerPublicKeys.SignedPreKey)
	sk := make([]byte, 0, 32*4)
	sk = append(sk, dh1[:]...)
	sk = append(sk, dh2[:]...)
	sk = append(sk, dh3[:]...)
	if peerPublicKeys.haveOneTimePreKey {
		dh4 := k.EphemeralKeyPair.GenerateShareKey(peerPublicKeys.OneTimePreKey)
		sk = append(sk, dh4[:]...)
	}
	k.EphemeralKeyPair.PrivateKey = [32]byte{}
	return sk
}

type SendKeyPackage struct {
	IdentityKey      [32]byte
	EphemeralKey     [32]byte
	OnetimeKeyNotice [32]byte
	Secret           []byte
}

func (k *X3DHKeyPairs) BuildSendKeyPackage(peerPublicKeys *X3DHPublicKeys, sharedKey []byte) *SendKeyPackage {
	b64 := base64.StdEncoding.EncodeToString(append(k.IdentityKeyPair.PublicKey[:], peerPublicKeys.IdentityKey[:]...))
	return &SendKeyPackage{
		IdentityKey:      k.IdentityKeyPair.PublicKey,
		EphemeralKey:     k.EphemeralKeyPair.PublicKey,
		OnetimeKeyNotice: peerPublicKeys.OneTimePreKey,
		Secret:           aes.Aes256GcmEncrypt([]byte(b64), sharedKey),
	}
}

func (k *X3DHKeyPairs) verify(keyPackage *SendKeyPackage, sk []byte) bool {
	b64 := base64.StdEncoding.EncodeToString(append(keyPackage.IdentityKey[:], k.IdentityKeyPair.PublicKey[:]...))
	_, err := aes.Aes256GcmDecrypt(keyPackage.Secret, sk, []byte(b64))
	if err != nil {
		return false
	}
	return true
}

func (k *X3DHKeyPairs) GetSharedKey(keyPackage *SendKeyPackage) []byte {

	dh1 := k.SignedPreKeyPair.GenerateShareKey(keyPackage.IdentityKey)
	dh2 := k.IdentityKeyPair.GenerateShareKey(keyPackage.EphemeralKey)
	dh3 := k.SignedPreKeyPair.GenerateShareKey(keyPackage.EphemeralKey)
	sk := make([]byte, 0, 32*4)
	sk = append(sk, dh1[:]...)
	sk = append(sk, dh2[:]...)
	sk = append(sk, dh3[:]...)
	if keyPackage.OnetimeKeyNotice != [32]byte{} {
		dh4 := k.OneTimePreKeyPair.GenerateShareKey(keyPackage.EphemeralKey)
		sk = append(sk, dh4[:]...)
	}
	if !k.verify(keyPackage, sk) {
		return nil
	}
	return sk
}
```
# 創作チーム

作者：Darren

校閲：Wayne、Yuki