倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

Go で量子鍵配送 (Quantum Key Distribution) B92 プロトコル

Go で量子コンピューティングシリーズ(目次)。 今回は B92 プロトコル

B92 プロトコルは BB84 プロトコルに比べて直感的に分かりにくい処理(エンコード・デコード*1)をしますが、古典チャネルでの通信が BB84 プロトコルに比べて半分で済みます。 その他、短い鍵ビットで盗聴に気づきやすいとか、古典チャネルでのやりとりの後で破棄するビットの割合が高いなどの違いがあります。

定量的な分析は次回に行い、今回は基本的な実装を見ていきます。

【この記事の内容】

量子鍵配送 B92 プロトコル

以下でビット列(bool 値のスライス)*2、qubit 列(qubit.Qubit のスライス)は全て長さが等しいとします。 B92 プロトコルの手順は以下のようになります:

  1. アリスはランダムなビット列 bits を生成し、これを qubit 列にエンコード(下記参照)して、これを量子チャネルでボブに送る。
  2. ボブはランダムなビット列 bits' を生成し、このビット列を使ってアリスから送られた qubit 列をデコード(下記参照)する。 デコードの結果得られたビット列を resultBits とする。
  3. ボブは resultBits を古典チャネルでアリスに送る。 また、ビット列 resultBits でビット値が0の位置の bits' のビットを破棄し、残ったビット列を鍵とする。
  4. アリスはボブから resultBits を受け取り、ボブと同じく resultBits でビット値が0の位置の bits のビットを破棄し、残ったビット列を鍵とする。
  5. 以上を、鍵が必要な長さになるまで繰り返す。

アリスがビット列 bits を qubit 列にエンコードする方法は

  { \displaystyle\begin{align*}
  0 &\mapsto |0\rangle \\
  1 &\mapsto |+\rangle = \frac{1}{\sqrt{2}}\left(|0\rangle + |1\rangle\right)
\end{align*}}

です。 また、ボブがビット列 bits' を使って qubit 列をビット列にデコードする方法は、bits' の各位置のビット値に対して、0ならアダマール基底で、1なら標準基底で対応する qubit を測定し、測定結果の状態から

  { \displaystyle\begin{align*}
  |+\rangle,\,|0\rangle &\mapsto 0 \\
  |-\rangle,\,|1\rangle &\mapsto 1
\end{align*}}

とビット値を割り当てます。

古典チャネルはボブからアリスへ resultBits を送るときだけ使うので、BB84 プロトコルの場合と比べて簡単です。 ただし、上記の手順で得たアリスとボブの鍵が一致することはあまり自明ではありません。 これが一致するのは以前の記事「『Quantum Computing: A Gentle Introduction』の演習問題を解く 2.11」で示しました。

Go での実装

では Go での実装を見ていきましょう。 まず、特に重要ではないアリスとボブの定型コードを見ておきましょう。 ほとんど BB84 プロトコルのものと同じです:

package b92

// NewAlice 関数は B92 プロトコルのアリスを返します。
func NewAlice(n int) *Alice { return &Alice{n, nil} }

type Alice struct{
  n int        // 鍵の長さ
  key qkd.Key  // 鍵ビット(実質的に []bool 型)
}

func (alice *Alice) Key() qkd.Key { return alice.key }

// NewAlice 関数は B92 プロトコルのボブを返します。
func NewBob(n int) *Bob { return &Bob{n, nil} }

type Bob struct{
  n int
  key qkd.Key
}

func (bob *Bob) Key() qkd.Key { return bob.key }

これを踏まえて、以下でアリスとボブの EstablishKey メソッドの実装を見ていきます。 ただし、次の関数は BB84 プロトコルの実装で出てきたものと同じものです:

  • qkd.NewRandomBits 関数:指定した長さのランダムな bool スライスを返します。
  • qkd.AppendMatchingBits 関数

AppendMatchingBits 関数は少々説明が面倒な関数で、もう少しリファクタリングした方が分かりやすいかも知れませんが、意外と使い回せるのでとりあえずそのまま使います。

b92.Alice の EstablishKey メソッド
まずはアリスの EstablisyKey メソッド: 

// EstablishKey メソッドはアリスがボブと通信して鍵を確立します。
func (alice *Alice) EstablishKey(ch qkd.ChannelOnAlice){
  for len(alice.key) < alice.n {
    // 一度に送信する qubit 列の長さはもう少し考えた方がいいかな。
    bits := qkd.NewRandomBits(alice.n)
    ch.Qch() <- encode(bits)

    resultBits := <- ch.FromBob()
    alice.key = qkd.AppendMatchingBits(alice.key, bits, resultBits, alice.n)
  }
}

// encode 関数は古典ビット列(スライス) bits を qubit 列にエンコードします。
func encode(bits []bool) []qubit.Qubit {
  var qbts = make([]qubit.Qubit, len(bits))
  for i, bit := range bits {
    if bit {  // 1 -> |+>
      qbts[i] = qubit.NewPlus()
    } else {  // 0 -> |0>
      qbts[i] = qubit.NewZero()
    }
  }
  return qbts
}

encode 関数で行っているエンコードの実装は特に難しいことはありませんね。

b92.Bob の EstablishKey メソッド
次はボブの EstablishKey メソッド:

// EstablishKey メソッドはボブがアリスと通信して鍵を確立します。
func (bob *Bob) EstablishKey(ch qkd.ChannelOnBob){
  for len(bob.key) < bob.n {
    qbts := <-ch.Qch()
    bits := qkd.NewRandomBits(len(qbts))

    resultBits := decode(qbts, bits)
    ch.ToAlice() <- resultBits

    bob.key = qkd.AppendMatchingBits(bob.key, bits, resultBits, bob.n)
  }
}

// decode 関数は qubit 列(スライス) qbts を古典ビット列にデコードします。
func decode(qbts []qubit.Qubit, bits []bool) []bool {
  resultBits := make([]bool, len(qbts))
  for i, qbt := range qbts {
    if bits[i] {  // 標準基底で測定
      resultBits[i] = qbt.ObserveInStandardBasis() == ket.One()
    } else {      // アダマール基底で測定
      resultBits[i] = qbt.ObserveInHadamardBasis() == ket.Minus()
    }
  }
  return resultBits
}

qubit.Qubit 型の ObserveInStandardBasis メソッドは標準基底で測定を行うメソッドです。 Observe(basis.Standard()) と同じです。 ObserveInHadamardBasis メソッドも同様。

鍵の確立
以上の実装を用いて、アリスとボブが鍵を確立するコードは以下のようになります:

  rand.Seed(time.Now().UnixNano())
	
  n := 50
  aliceKey, bobKey := qkd.EstablishKeys(NewAlice(n), NewBob(n))

  fmt.Printf("アリスの鍵:%s\n", aliceKey)
  fmt.Printf("ボブの鍵 :%s\n", bobKey)
  fmt.Println(aliceKey.Equals(bobKey))

これを実行すると

アリスの鍵:11111111100011100010101111011100001100010100010000
ボブの鍵 :11111111100011100010101111011100001100010100010000
true

などと表示され、アリスとボブで同じ鍵が確立されることが分かります。

盗聴

B92 プロトコルで盗聴して鍵を生成するイヴは次回に見ていくことにして、今回は単に測定を行うだけのイヴを使って、アリスとボブの確立した鍵が異なり盗聴に気づくことを見てみましょう。 盗聴した qubit 列を標準基底もしくはアダマール基底のどちらかをランダムに用いて測定するイヴは「Go で量子鍵配送 (Quantum Key Distribution) もう少し BB84 プロトコル」で見たので、それを使い回します。 このイヴは qkd.NewObservingEve 関数で生成します:

  rand.Seed(time.Now().UnixNano())

  n := 50
  aliceKey, bobKey, _ := qkd.EstablishKeysWithEavesdropping(
      NewAlice(n), NewBob(n), qkd.NewObservingEve())

  fmt.Printf("アリスの鍵:%s\n", aliceKey)
  fmt.Printf("ボブの鍵 :%s\n", bobKey)
  fmt.Println(aliceKey.Equals(bobKey))

これを実行すると

アリスの鍵:01010111110101100010001101011010101111011010111010
ボブの鍵 :10100111000101100010001101001011101110010110011110
false

のようにアリスとボブで異なる鍵が生成されます。

盗聴して鍵を生成するイヴや、もう少し定量的な分析は次回に行います。

Quantum Computing: A Gentle Introduction (Scientific and Engineering Computation)

Quantum Computing: A Gentle Introduction (Scientific and Engineering Computation)

*1:B92 プロトコルエンコード・デコードという語を使うのかどうか分かりませんが。

*2:ビットを破棄する前のビット列。