Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

ビットコインでは楕円曲線暗号secp256k1が使われている

どうやらビットコインでは署名に楕円曲線暗号が使われているらしいです。
楕円曲線暗号というのはRSA暗号と同じ非対称型の暗号で秘密鍵と公開鍵のペアからなります。
秘密鍵は自分だけが知っている状態にし、公開鍵は他人に公開します。

ビットコインとは何か? 第3回:ビットコインの仕組み(アドレスの作成から送金まで) - ビットコインの解説 | Bitcoin日本語情報サイト

使われている楕円曲線暗号の形式はsecp256k1で、
曲線は {y}^{2} = {x}^{3} + 7だそうです。
法とする素数 {2}^{256} - {2}^{32} - {2}^{9} - {2}^{8} - {2}^{7} - {2}^{6} - {2}^{4} - 1 ( = {2}^{256} - {2}^{32} - 977 )
ベースポイント、ベースポイントの位数Lも定義されています。
cofactorは1ですね。

Secp256k1 - Bitcoin Wiki

その他のコインはどうなっているのか調べてみると、
イーサリウムはビットコインと同じsecp256k1のようです。
楕円曲線暗号に使う楕円曲線、法素数、ベースポイントは、暗号学者やNISTが安全だと認めたもののうちから使うのが普通です。
世の中に出回っている仮想通貨の多くはsecp256k1とed25519のどちらかを利用しているようです。

secp256k1の実装

bitcoinで使われているsecp256k1のソースはC言語で書かれていますが、
現在のメンテナーは、bitcoin core開発者の一人である Pieter Wuille 氏です。
C言語による実装ソースはこちら

GitHub - bitcoin-core/secp256k1: Optimized C library for EC operations on curve secp256k1

処理時間を気にしなければ、この暗号をpythonで実装することも容易で、数百行で実装出来てしまいます。
ただし、処理時間がめちゃくちゃかかり実用に耐えませんでので、実際は、
演算を高速に行うための様々なアルゴリズムを駆使した上、さらにアセンブリを使い、C言語で最も高速に動作するところまで、
ギリギリにチューニングしてあります。
暗号の演算を高速に行うためのアルゴリズムはそれだけで論文になるほどで、
大学4年生レベルの数学の知識が必要になります。

OpenSSHにed25519が追加された経緯

この調査の過程で、OpenSSHにed25519が追加された経緯を知りました。

leah blogs: The road to OpenSSH bliss: ED25519 and the IdentityPersist patch

NISTというアメリカの暗号学者が多数所属する国家機関であるNSA
(マットデイモン脚本、主演の映画 グッドウィルハンティングにも出てきましたね。)
に関連のある団体が決めた楕円曲線は何か重大なバックドアがあるかもしれない。
心配だからed25519も追加しようぜってことらしい。どんだけパラノイアなんだって話ですが。

この曲線を選んだのは Satoshi Nakamoto

こちらの記事や、
NSA Backdoors and Bitcoin | Escape Velocity

Vitalik Buterin による2013年の記事

Satoshi’s Genius: Unexpected Ways in which Bitcoin Dodged Some Cryptographic Bullets - Bitcoin Magazine - Bitcoin News, Articles and Expert Insights によると、

署名のアルゴリズムとして、secp256k1 を選んだのは Satoshi Nakamoto のようです。
secp256k1 のkは有名な楕円曲線論研究者のKobliz先生の頭文字から来ています。
似た楕円曲線としてsecp256r1があります。
secp256k1ではxの係数が0で定数項が7ですが、secp256r1はxの係数も定数項も巨大な数です。
この数はNISTが定めたもののようですが、NSAによるバックドアが仕掛けられているかも知れないので、
Bitcoinでは採用されなかったのではないかと推測されているようです。
一般に知られている安全な楕円曲線の選び方としては、
こちらの本に記載されているように、

知られている攻撃法を回避するようにします。
1)  \# E(F_q) は大きな素数Lと小さな正整数hで \# E(F_q)=L \cdot hとなること。
2) L と p は互いに素であること。
3)  E(F_q)の位数がLの元をGとするとき、 \langle G \rangle の双線型写像による {F_q}^{k}への埋め込み次数kに対して以下のいずれかを満たす。
(a)  1 \lt k \lt {(\log q)}^{2} の整数kに対し、 L \nmid ({q}^{k} - 1 ) である。
(b)  L \mid ( {q}^{k} - 1)となるkが 6 \leq k \lt 20である。

supersingular曲線ではないことの確認

 p = 2^{256} - 2^{32} - 977 ですが、
 y^{2} = x^{3} + Ax + B
A=0なのでこの曲線がsupersingularでないことを確かめます。
 p = 1 (mod 3)
であることがpythonで簡単に確かめられます。
 p = 2 (mod 3)ではないのでsupersingularではありません。

ed25519のpython実装を紐解く その2 暗号編キーペア生成からベリファイまで

前回

ed25519のpython実装を紐解く その1数学編 - Pebble Coding

はed25519のpython実装の数学編をやりましたが、今回は暗号編です。
暗号部分の関数を見ていきます。
これらの計算手順は前回と同じRFCに記載されています。

概念図を書いてみました。

f:id:pebble8888:20180417234216p:plain

b = 256

秘密鍵の長さは256ビットつまり、32バイトのバイト列で表現できます。
公開鍵の長さも同じ32バイトのバイト列です。

秘密鍵は単純にランダムな32バイトのバイト列を生成すればそれで終わりです。(マジか)
公開鍵は秘密鍵から算出します。この手順は後で説明します。

def H(m):
  return hashlib.sha512(m).digest()

H()関数では、任意の長さのバイト列mのsha512を計算します。digest()関数は512ビットつまり、64バイトのバイト列を返します。

def bit(h,i):
  return (ord(h[i/8]) >> (i%8)) & 1

bit()関数ではバイト列hの上位(左側)から数えて(i/8)番目のバイトの、下位から数えて(i%8)ビット目が1か0かを返す関数です。
ordは1文字を整数のコードポイントに変換する関数です。

秘密鍵、公開鍵ペアの生成 (key pair creation)

def publickey(sk):
  h = H(sk)
  a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
  A = scalarmult(B,a)
  return encodepoint(A)

publickey()関数は秘密鍵の32バイト列から公開鍵の32バイト列を返す関数です。
STEP1) 32バイト列の秘密鍵のsha512を計算し64バイト列を出力しhとします。
鍵生成にはhの上位32バイトのみを使います。
STEP2) 最上位バイトの最下位3ビットをクリアして8の倍数にします。
最下位(上位から32バイト目)の最上位ビットから2番目をセットします。
これは桁数を保証するためです。
最上位は0です。qより小さい値にするためです。

f:id:pebble8888:20171009002335p:plain:w500

STEP3) 32バイト列をLittleEndianの正の整数(10進数で77桁)としてaとします。
以降LittleEndian整数化するときは常に正の整数とします。
ベースポイントBをスカラーa倍して点Aを計算します。
STEP4) 点Aの点座標を使ってある計算をします。
計算はencodepoint関数で実装します。
戻り値は32バイト列で公開鍵となります。

def encodepoint(P):
  x = P[0]
  y = P[1]
  bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
  return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])

encodepoint(P)関数は点Pのx,y値を用いて、32バイト列を返します。
 q={2}^{255}-19なので、点の座標は256ビット未満つまり32文字以内であることを理解しておきます。

点Pのy座標値の整数をLittleEndianの32文字で表現します。
x,y座標は常に0以上、 {2}^{255}-19未満なので、256ビットの最上位ビットは常に0です。
そして256ビットのうちの最下位ビットをx座標値の最下位ビットの値に置き換えます。
こうしてできた0,1の256個の配列がbitsでそれを文字列に変換した32バイト列が戻り値です。

f:id:pebble8888:20171007000258p:plain:w500

def encodeint(y):
  bits = [(y >> i) & 1 for i in range(b)]
  return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])

関数encodeint(y)はy座標の整数値(256ビット)をLittleEndianとして32バイト列に変換します。

署名(signing)

def signature(m,sk,pk):
  h = H(sk)
  s = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
  r = Hint(''.join([h[i] for i in range(b/8,b/4)]) + m)
  R = encodepoint(scalarmult(B,r))
  k = Hint(R + pk + m)
  S = (r + k * s) % l
  return R + encodeint(S)

signature()関数は、秘密鍵sk、公開鍵pkを使って任意の長さのバイト列mに署名(sign)した64バイト列を返します。

STEP1) 32バイトの秘密鍵のsha512を計算し64バイト列を出力します。これをhとします。
hの前半の半分の32バイトのバイト列に対して、最下位バイトの最下位3ビットをクリアします。
最後のバイト(下位から32バイト目)の最上位ビットをセットします。これをsとします。
STEP2) hの後半の32バイトに任意の長さのバイト列mを加え、sha512を計算して出力した64バイト列をLittleEndianで整数(512bitなので10進数で約155桁)にして、これをrとします。
STEP3) ベースポイントBをrスカラー倍した点のencodepointを計算し、32バイト列を作りRとします。rは10進数で約155桁ほどです。
STEP4) 32バイト列のR、32バイト列の公開鍵、任意のバイト列のmを結合して、sha512を取り、LittleEndianとして64bitの整数にしてkとします。
STEP5) S = (r + k * s) mod Lを計算します。
STEP6) 32バイトのRとSのLittleEndian化した32バイトを結合した64バイト列を署名とします。

def Hint(m):
  h = H(m)
  return sum(2**i * bit(h,i) for i in range(2*b))

Hint()関数は任意長のバイト列のsha512を取って512ビット(=64バイト)のバイト列をLittleEndianとしての整数を返します。

Verification(署名確認)

def checkvalid(s,m,pk):
  if len(s) != b/4: raise Exception("signature length is wrong")
  if len(pk) != b/8: raise Exception("public-key length is wrong")
  R = decodepoint(s[0:b/8])
  A = decodepoint(pk)
  S = decodeint(s[b/8:b/4])
  k = Hint(encodepoint(R) + pk + m)
  if scalarmult(B,S) != edwards(R,scalarmult(A,k)):
    raise Exception("signature does not pass verification")

64バイト列の署名s、任意長のメッセージm、32バイト列の公開鍵pkを用いて、このメッセージを署名確認し、
確認できない場合は例外を送出します。
STEP1) 署名の前半32バイト列を点Rにデコードします。
公開鍵を点Aにデコードします。
署名の後半32バイト列をLittleEndianで正の整数Sにデコードします。(Sは0以上L未満です。)
STEP2) 点Rをエンコードした32バイト列、32バイト列の公開鍵、任意長のメッセージバイト列を結合し、512ビットの整数kとします。
STEP3) ベースポイントBをSスカラー倍した点と、点Rに、点Aをkスカラー倍した点を加算した点が等しいかどうかを確かめます。

def decodeint(s):
  return sum(2**i * bit(s,i) for i in range(0,b))

decodeint()関数は、encodeint()の逆関数で、32バイト列をLittleEndian整数に変換します。

def decodepoint(s):
  y = sum(2**i * bit(s,i) for i in range(0,b-1))
  x = xrecover(y)
  if x & 1 != bit(s,b-1): x = q-x
  P = [x,y]
  if not isoncurve(P): raise Exception("decoding point that is not on curve")
  return P

decodepoint()関数はencodepoint()関数の逆関数で、
32バイト列sからy座標の値に変換します。256ビットの最上位ビットは常に0なので、無視します。
このy座標の値からx座標の値を求めます。x座標の値の偶奇が一致しない場合は、一致したxの値に置き換えます。
これが点Pの座標となるので関数の戻り値としますが、ここでは攻撃を防ぐためさらに、楕円関数上の点であることを確認しています。

秘密鍵を使ってベースポイントをaスカラー倍した点Aの座標は公開するけれども、
どのくらいスカラー倍したかは公開しないとしていることが分かります。
また、ベースポイントをLスカラー倍すると無限遠点になることを用いていることも分かります。

なお、下位3ビットをクリアして8の倍数にしている理由はこちらを参照ください。
この曲線のcofactorが8であることと関係があります。
 8 L = \#E

ed25519におけるcofactor=8 - Pebble Coding

ed25519のpython実装を紐解く その3 暗号編テスト - Pebble Coding

ed25519のpython実装を紐解く その1数学編

ed25519のpythonによるリファレンス実装を解説してみます。
pythonのリファレンス実装はこちらです。
https://ed25519.cr.yp.to/python/ed25519.py

数学的な関数の解説のみ簡単に行います。
詳しくはこのブログの他の記事に記載があります。
使う楕円曲線はed25519と呼ばれる、Twisted Edward曲線です。

q = 2**255 - 19
L = 2**252 + 27742317777372353535851937790883648493

素数2255-19=57896044618658097711785492504343953926634992332820282019728792003956564819949
=0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
は10進数で77桁の素数です。
曲線の方程式は - {x}^{2} + {y}^{2} = 1 - \frac {121665} {121666}  {x}^{2} {y}^{2} という形をしており、xが一つ決まるとyが一つ決まりますが、x,yは共に整数しか取りません。
そして、 - \frac {121655} {121666}は分数ではなく整数であり、
 - \frac {121655} {121666} = 37095705934669439343138083508754565189542113879843219016388785533085940283555 という10進数で77桁の整数です。
この方程式は素数qを法とし成り立つものとして考えます。
なぜ割り算で表現してあるかというと、その方が77桁の整数を全てを書くよりも紙面が節約できるからです。
なぜ割り算がこうなるのかというと、素数を法とした演算を使うと体を成すことが知られているためです。
これを素体といいます。
整数の範囲内での演算なので、割り算は掛け算を使って表現します。

LはこちらのRFC8032
RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA)
に書かれている通り、 ed255199曲線の有理点の数(#Eと表現します。)の約数である素数であり、
 L = {2}^{252}  + 27742317777372353535851937790883648493
= 7237005577332262213973186563042994240857116359379907606001950938285454250989
= 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed
という10進で76桁の整数です。
なお、 8 L = \#E です。
Hasse-Weilの定理を考えると、有理点の数がこのくらいあることは納得感があります。
つまり有理点の数は10進で77桁くらいあるということです。(※1)
また、このLはある条件を満たしますが、これは後で説明します。

def expmod(b,e,m):
  if e == 0: return 1
  t = expmod(b,e/2,m)**2 % m
  if e & 1: t = (t*b) % m
  return t

expmod関数では高速指数計算法で法mにおいてbのe乗を計算する関数です。

def inv(x):
  return expmod(x,q-2,q)

inv関数では法qにおけるxの逆数を計算する関数です。
なぜq-2乗すると逆数になるのかはオイラーの小定理を考えると理解できます。

d = -121665 * inv(121666)

そのままですね。

I = expmod(2,(q-1)/4,q)

これは、ここで平方剰余を計算するときのケースAのときの係数です。

mod pでの平方剰余を計算する(p mod 8 = 5の場合)を実装する - Pebble Coding

def xrecover(y):
  xx = (y*y-1) * inv(d*y*y+1)
  x = expmod(xx,(q+3)/8,q)
  if (x*x - xx) % q != 0: x = (x*I) % q
  if x % 2 != 0: x = q-x
  return x

xrecovery関数は、曲線上のyの値が与えられたときにxの値を求める関数です。
単純にed25519の方程式をxについてまとめると最初の式になり、 {x}^{2} = aを解く形になります。
この解き方は先ほどのリンクで説明しています。
xが奇数になったときは、奇素数qを使って反転して、q(奇数)-x(奇数) = 偶数に変換しています。
xが解であるとき、q-xが解であることは、
 {(q-x)}^{2} = {q}^{2} - 2qx + {x}^{2} =  {x}^{2} \mod qであることから分かります。

By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx % q,By % q]

これもRFCに記載されていますが、ベースポイントの座標です。 実際の値は次のような値です。
Bx = 15112221349535400772501151409588531511454012693041857206046113283949847762202
77桁の10進数

By = 46316835694926478169428394003475163141307993866256225615783033603165251855960
77桁の10進数

ベースポイントBとは点のスカラー倍を計算していくときの最初の点です。
点BをLスカラー倍したときにゼロ点(x=0, y=1)になります。
通常の楕円曲線ではゼロ点は無限遠点ですが、この曲線ではこのような有限の値の点になるので、
コンピュータでの計算が分かりやすくなっています。
Lは最初の方で出てきた値です。
つまり  L B = 0と表現できます。 点Bの位数はLであるともいいます。

これらの条件は、楕円曲線暗号において知られている攻撃方法を回避するように設定されています。
新たな攻撃方法が発見されない限り、安全だと言えます。

def edwards(P,Q):
  x1 = P[0]
  y1 = P[1]
  x2 = Q[0]
  y2 = Q[1]
  x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2)
  y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
  return [x3 % q,y3 % q]

edwards関数では点Pと点Qを加算した点の座標を求めています。

def scalarmult(P,e):
  if e == 0: return [0,1]
  Q = scalarmult(P,e/2)
  Q = edwards(Q,Q)
  if e & 1: Q = edwards(Q,P)
  return Q

scalarmult関数では点Pをe倍した点の座標を求めます。
これは、倍数を高速に計算する方法を再帰で実装したものです。 このリンク
高速指数計算法アルゴリズム - Pebble Coding
ではべき乗の場合ですが、倍数の場合も同じようなロジックになります。

def isoncurve(P):
  x = P[0]
  y = P[1]
  return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0

isoncurve関数は点Pが楕円曲線上にあるかどうかを返す関数です。

ここでみたように楕円曲線暗号は初等整数論、楕円関数論を活用して作られていることが分かります。
大学の数学科4年生レベルの数学知識が求められ、理解するのに一筋縄ではいきません。

※1 この有理点の数を求めるのにどのくらい時間がかかるのか想像できていません。
mac book proでは数分レベルでないことは分かっていますが、誰か調べて欲しい。。

ed25519のpython実装を紐解く その2 暗号編キーペア生成からベリファイまで - Pebble Coding

Twisted Edward曲線における加法

Twisted Edward曲線 
- x^{2} + y^{2} = 1 + d x^{2}y^{2}

において、曲線上の2つの点  (x_{1}, y_{1}), (x_{2}, y_{2}) の加算後の点を次のように定義する。

 \displaystyle X = \frac {x_{1} y_{2} + x_{2} y_{1}} {1 + d x_{1} x_{2} y_{1} y_{2}}

 \displaystyle Y = \frac {x_{1} x_{2} + y_{1} y_{2}} {1 - d x_{1} x_{2} y_{1} y_{2}}

この点は代数計算によって、Twisted Edward曲線上の点になることを確かめることができる。
が、計算は長く厄介なので、計算方法を記しておく。
計算のコツとしては常に x_{1} x_{2} y_{1} y_{2}の順序でまとめておくことである。   

 A = - X^{2} + Y^{2} - d X^{2}Y^{2}を計算し、 1に等しくなることを示す方針で行く。

代入すると  \displaystyle A = \frac {1} {(1 + d x_{1} x_{2} y_{1} y_{2})^{2}} \cdot \frac {1} {(1 - d x_{1} x_{2} y_{1} y_{2})^{2}} \cdot ( \\ - (x_{1}y_{2} + x_{2}y_{1})^{2}(1- d x_{1}x_{2}y_{1}y_{2})^{2} \\ + (x_{1}x_{2} + y_{1}y_{2})^{2} (1 + d x_{1} x_{2} y_{1} y_{2})^{2} \\ - d (x_{1}y_{2} + x_{2}y_{1})^{2} (x_{1}x_{2} + y_{1}y_{2})^{2})

ここで、

 \displaystyle A(1 - d^{2} x_{1}^{2} x_{2}^{2} y_{1}^{2} y_{2}^{2})^{2} = B + C + Dと置く。

B + C + Dを全て展開し、 x_{1}, x_{2}, y_{1}, y_{2}の順番で並べると、7つほどペアが消える。

 B + C + D = - x_{1}^{2}y_{2}^{2} - x_{2}^{2}y_{1}^{2} + x_{1}^{2} x_{2}^{2} + y_{1}^{2} y_{2}^{2} \\
- d x_{1}^{4}x_{2}^{2}y_{2}^{2} - d x_{1}^{2}x_{2}^{4}y_{1}^{2} - d x_{1}^{2}y_{1}^{2}y_{2}^{4} - d x_{2}^{2}y_{1}^{4}y_{2}^{2} + 4 d x_{1}^{2}x_{2}^{2}y_{1}^{2}y_{2}^{2}  \\
- d^{2} x_{1}^{4}x_{2}^{2}y_{1}^{2}y_{2}^{4} - d^{2} x_{1}^{2}x_{2}^{4}y_{1}^{4}y_{2}^{2} + d^{2} x_{1}^{4}x_{2}^{4}y_{1}^{2}y_{2}^{2} + d^{2} x_{1}^{2}x_{2}^{2}y_{1}^{4}y_{2}^{4}  \\
= E_{4} + E_{8} + E_{12}

ここでE_{i}はx,yの個数を表すが、それぞれ別々に計算してゆくと、 E_{4}, E_{8}, E_{12}には、  - x_{1}^{2} + y_{1}^{2},  - x_{2}^{2} + y_{2}^{2}が2つ以上出てくるので、  - x_{1}^{2} + y_{1}^{2} = 1 + d x_{1}^2 y_{1}^2
 - x_{2}^{2} + y_{2}^{2} = 1 + d x_{2}^2 y_{2}^2 に置き換える。

ここで、   E_{4} + E_{8} + E_{12}を計算すると、  1 - 2 d^{2} x_{1}^{2}x_{2}^{2}y_{1}^{2}y_{2}^{2} +  d^{4} x_{1}^{4}x_{2}^{4}y_{1}^{4}y_{2}^{4}が残る。 あとは自明である。

swift3でCoreAudioを使う 録音編

CoreAudio/swift3で録音を行うサンプルです。
iPhoneのマイクから取得した音声データをレベル値に変換して画面に表示します。

import Foundation
import AVFoundation
import AudioUnit

class MyAudioRecorder: NSObject {
    var level: Float  = 0.0
    var frameCount: UInt32 = 0

    private var _audioUnit: AudioUnit?   
    private var _abl: AudioBufferList?
    private let kInputBus: UInt32 =  1
    private let kNumberOfChannels: Int =  1
    
    func start() {
        // AudioSession セットアップ
        do {
            let audioSession = AVAudioSession.sharedInstance()
            try audioSession.setCategory(AVAudioSessionCategoryRecord)            
            try audioSession.setActive(true)
        } catch {
        }
        // CoreAudio セットアップ
        var componentDesc:  AudioComponentDescription
            = AudioComponentDescription(
                componentType:          OSType(kAudioUnitType_Output),
                componentSubType:       OSType(kAudioUnitSubType_RemoteIO),
                componentManufacturer:  OSType(kAudioUnitManufacturer_Apple),
                componentFlags:         UInt32(0),
                componentFlagsMask:     UInt32(0) )
        
        let component: AudioComponent! = AudioComponentFindNext(nil, &componentDesc)
        var tau: AudioUnit?
        AudioComponentInstanceNew(component, &tau)
        _audioUnit = tau
        
        guard let au = _audioUnit else { 
            return
        }
        // RemoteIO のマイクを有効にする
        var enable: UInt32 = 1
        AudioUnitSetProperty(au,
                             kAudioOutputUnitProperty_EnableIO,
                             kAudioUnitScope_Input,
                             kInputBus,
                             &enable,
                             UInt32(MemoryLayout<UInt32>.size))

        // マイクから取り出すデータフォーマット
        // 32bit float, linear PCM
        guard let fmt = AVAudioFormat(standardFormatWithSampleRate: 44100, 
                                      channels: UInt32(kNumberOfChannels)) else {
            return
        }
        
        // RemoteIO のマイクバスから取り出すフォーマットを設定
        AudioUnitSetProperty(au,
                             kAudioUnitProperty_StreamFormat,
                             kAudioUnitScope_Output,
                             kInputBus,
                             fmt.streamDescription,
                             UInt32(MemoryLayout<AudioStreamBasicDescription>.size))

        // AudioUnit に録音コールバックを設定
        var inputCallbackStruct
            = AURenderCallbackStruct(inputProc: recordingCallback,
                                     inputProcRefCon:
                UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
        AudioUnitSetProperty(au,
                             AudioUnitPropertyID(kAudioOutputUnitProperty_SetInputCallback),
                             AudioUnitScope(kAudioUnitScope_Global),
                             kInputBus,
                             &inputCallbackStruct,
                             UInt32(MemoryLayout<AURenderCallbackStruct>.size))
        
        // データ取り出し時に使う AudioBufferListの設定
        _abl = AudioBufferList(
            mNumberBuffers: 1,
            mBuffers: AudioBuffer(
                mNumberChannels: fmt.channelCount,
                mDataByteSize: fmt.streamDescription.pointee.mBytesPerFrame,
                mData: nil))

        AudioUnitInitialize(au)
        AudioOutputUnitStart(au)
    }
    
    let recordingCallback: AURenderCallback = { (
        inRefCon,
        ioActionFlags,
        inTimeStamp,
        inBusNumber,
        frameCount,
        ioData ) -> OSStatus in
        
        let audioObject = unsafeBitCast(inRefCon, to: MyAudioRecorder.self)

        if let au = audioObject._audioUnit {
            // マイクから取得したデータを取り出す
            AudioUnitRender(audioObject._audioUnit!,
                                ioActionFlags,
                                inTimeStamp,
                                inBusNumber,
                                frameCount,
                                &audioObject._abl!)
        }
        audioObject.frameCount = frameCount
        let inputDataPtr = UnsafeMutableAudioBufferListPointer(&audioObject._abl!)
        let mBuffers: AudioBuffer = inputDataPtr[0]
        let bufferPointer = UnsafeMutableRawPointer(mBuffers.mData)
        if let bptr = bufferPointer {
            let dataArray = bptr.assumingMemoryBound(to: Float.self)
            // マイクから取得したデータからRMS(RootMeanSquare)レベルを計算する
            var sum:Float = 0.0
            if frameCount > 0 {
                for i in 0 ..< Int(frameCount) {
                    sum += (dataArray[i]*dataArray[i])
                }
                audioObject.level = sqrt(sum / Float(frameCount))
            }
        }
        return 0
    }
}

まず、Info.plistに
Privacy - Microphone Usage Description
の項目を追加します。
これを行わないとiPhoneのマイクが使えません。

次に、オーディオセッションのカテゴリを録音にします。

let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(AVAudioSessionCategoryRecord)            
try audioSession.setActive(true)

録音を行う RemoteIO を作り、RemoteIO のマイクを有効にします。

マイクから取り出すデータフォーマットを設定します。
ここでは、32bit float モノラル サンプルレート44.1kHzとしました。 RemoteIOに録音コールバック関数を設定します。

録音を開始すると、例えば1024サンプル録音される毎にこのコールバック関数が呼ばれるので、 コールバック関数内でバッファからデータを取り出して処理します。

ここでは、取り出したデータの平均値(RMS)を計算します。
ViewController上にはこの値を画面に表示しています。

以下、ViewController のソースです。

class ViewController: UIViewController {
    @IBOutlet var progress:UIProgressView!
    @IBOutlet var label:UILabel!
    
    var recorder:MyAudioRecorder!
    var timer: Timer!
    override func viewDidLoad() {
        super.viewDidLoad()
        recorder = MyAudioRecorder()
        recorder?.start()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        timer = Timer.scheduledTimer(timeInterval: 0.005, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
        timer.fire()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        timer.invalidate()
    }
    
    @objc func update(tm: Timer){
        progress.progress = recorder.level
        label.text = "\(recorder.frameCount)"
    }    
}

録音を開始した後、5ms間隔でレベル値を画面に表示するようにしています。

ソースはこちら https://github.com/pebble8888/AudioRecorderSwift

参考 Swift 3.0 Audio Recording class. Reads buffers of input samples from the microphone using the iOS RemoteIO Audio Unit API · GitHub

Macデジタルオーディオプログラミング (Mynavi Advanced Library)

Macデジタルオーディオプログラミング (Mynavi Advanced Library)