iOSでNEMのAPIを使って送金する
先日 Android で NEM の API を使うサンプルを作りましたが、今回はその iOS版を作りました。
ロジックは Android/iOS で変わることはないのですが、環境設定や使うライブラリなどは変わってきますので、そのあたりをメインに書きます。
前知識とかは同じなので、先に Android版の記事を読んでいただくとよりわかりやすいかと思います。
注意点
念のため。
- 今回作成したアプリは、秘密鍵を端末内に平文で保存しています。実際のアプリでは暗号化するなどにして厳重に管理しましょう
- 今回作成したコードを使って何か損害が発生しても一切責任は取りませんので、全て自己責任でお願いします。
作ったアプリ
Android版と同じ、超簡易ウォレットです。アカウント情報の確認と、XEM の送金ができます。
コード解説
全文は GitHub にあげています。ご参照ください。
READMEにも書いてますが、git の submodule を使ってますので、clone する際は --recursive
つけて clone してください。
1 2 |
$ git clone --recursive https://github.com/ryuta46/nem-api-test-ios.git |
ATS の無効化
NEM の API サーバ(NIS) は平文の HTTP にしか対応していないようです。
iOS9 から、ATS(App Transport Security) により HTTPS でないリクエストが内部でブロックされてしまいますので、先にこの機能を無効化しておきます。
Xcode プロジェクトの Info.plist で「App Transport Security Settings」という設定を追加し、その中で「Allow Arbitrary Loads」を YES に設定しておきます。
この設定は、どのホストに対しても HTTP のアクセスを許可する設定になりますので、通信するホストが決まっていればもっと範囲を狭めて許可することも可能です。
詳細は Apple の公式ページを参照ください。
Ed25519 + SHA3-512 環境の構築
Android版の方の記事に書いた通り、NEM で使っている暗号化アルゴリズムは Ed25519 で、ハッシュアルゴリズムは SHA3-512 です。
iOS でも Ed25519 + SHA3-512 を使えるようにすることが必要です。
全て Pure Swift の環境でこれらの実装を揃えるのは難しいので、C のライブラリを Swift から呼び出す方法で実装しました。
依存ライブラリの選定
NEM Wallet の iOS版では、Ed25519 の実装として下記ライブラリを、
SHA3-512 の実装としては下記ライブラリのコードの一部を使って実装しているようです。
Ed25519 のライブラリは同じものが使えそうでしたが、SHA3IUF の方はコードがライブラリ化されておらず扱いにくかったので、本アプリでは RHash というライブラリを使います。
依存ライブラリの取得
ed25519、RHash とも GitHub にソースコードはありますが、Objective-C や Swift のライブラリではないため、CocoaPods のようなパッケージマネージャは使えません。ソースを取得して自分でビルドする必要があります。
というわけで、git から直接ダウンロードしてプロジェクト内の適当な場所に展開してください。
本アプリでは、git の submodule としてこれらのライブラリを追加しています。
依存ライブラリのコードを直接プロジェクト内に入れたくなかったので、外部参照するような形にして git clone --recursive
で依存ライブラリごと取得できるようにしています。
依存ライブラリのビルド
取得したソースコードは Xcode でビルド対象に加えればビルドできますが、全てのコードを含めるとテストコードなどでビルドエラーになってしまいますので、必要なソースだけ抜粋してビルドするようにします。
それぞれのライブラリで必要なコードは以下
- ed25519
src/ 以下全て - RHash
librhash/ 以下の byte_order.h、byte_order.c、sha3.h、sha3.c
Xcode のプロジェクトしては下記のような構成になるかと思います。
また、ビルドする際、プリプロセッサの定義として USE_KECCAK を定義してください。
話それますが、USE_KECCAK について少し補足。
RHash には、sha3 のハッシュ値を取得する関数と、keccakのハッシュ値を取得する関数が存在しています。
SHA-3 は元は Keccak という名前だったといろんなところに書いてあったので同じものを指しているんじゃないのかと思ったのですが、RHash が実装している sha3 の関数はドラフト版の仕様に基づいて作られたもので、Keccak とは計算結果が変わるみたいです。
現在は Keccak の実装が SHA3 として標準化されているので、上記プリプロセッサ定義を用いて Keccak の結果を得ないと NEM の期待するキーペアになってくれませんでした。
・・・と、下記 issue を見て理解しています。誰か下記ページ翻訳してください・・・
Ed25519 + SHA3-512 関数の作成
ed25519 のライブラリは、Ed25519 のリファレンス実装をポーティングしたものなので、内部のハッシュ関数は SHA-512 です。
これを SHA3-512 のハッシュを使うようにしたいところですが、ハッシュ関数を差し替えるような機能はこのライブラリにはありません。
よって、SHA-512 の代わりに SHA3-512 を使う関数を独自で実装します。依存ライブラリがどちらもCなので、この独自関数も C のレイヤーで実装したほうが良いかと思います。
例えば、ed25519 の公開鍵作成の実装は下記のようになっていますので、
1 2 3 4 5 6 7 8 9 10 11 12 |
void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) { ge_p3 A; sha512(seed, 32, private_key); private_key[0] &= 248; private_key[31] &= 63; private_key[31] |= 64; ge_scalarmult_base(&A, private_key); ge_p3_tobytes(public_key, &A); } |
これを RHash の SHA3 を使うようにした独自関数 ed25519_sha3_create_keypair 関数を作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void ed25519_sha3_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) { ge_p3 A; sha3_ctx ctx; rhash_keccak_512_init(&ctx); rhash_keccak_update(&ctx, seed, 32); rhash_keccak_final(&ctx, private_key); private_key[0] &= 248; private_key[31] &= 63; private_key[31] |= 64; ge_scalarmult_base(&A, private_key); ge_p3_tobytes(public_key, &A); } |
署名作成も同様に。
面倒くさかったら GitHub にあげたコードをコピペするなりして使って下さい。
本アプリのコードではキーペアの検証を入れている関係で、署名検証でSHA3-512を使うコードも入れています。
Swiftから呼び出す準備
作成した Ed25519 + SHA3 の関数を Swift から呼べるようにします。
プロジェクト名-Bridging-Header.h のような名前でヘッダファイルを作成し、先程作った関数の宣言を import する処理を書いておきます。
今回、秘密鍵の作る関数はed25519のライブラリ内のものを使っても良いかなと思ったので、
ed25519.h も import してます。
1 2 3 4 5 6 7 8 |
#ifndef NemApiTestiOS_Bridging_Header_h #define NemApiTestiOS_Bridging_Header_h #import "ed25519/src/ed25519.h" #import "ed25519_sha3_512.h" #endif /* NemApiTestiOS_Bridging_Header_h */ |
作成したヘッダファイルを、Xcode の Swift Compiler の設定で Objective-C Bridging Header として指定します。
これで Swiftから先程作成した関数が呼び出せるようになります。
Swift から呼び出す時は、Cのポインタは UnsafeMutablePointer<> のようなクラスとして見えます。
そのままだと扱いにくいので、適宜 Swift の配列に変換するなどして使えばよいかと思います。
本アプリでは ConvertUtil.swift などで変換してます。
アプリの Swift コード
ここまで来ると、後はほぼ Android版とやることは同じです。
Swift から 秘密鍵を作成するには、ed25519_create_seed を呼び出し、
1 2 3 4 |
let nativeSeed = UnsafeMutablePointer<UInt8>.allocate(capacity: 32) ed25519_create_seed(nativeSeed) privateKeySeed = ConvertUtil.toArray(nativeSeed, 32) |
キーペアの作成は先程作成した関数 ed25519_sha3_create_keypair を呼び出すだけです。
1 2 3 4 5 |
let privateKey = UnsafeMutablePointer<UInt8>.allocate(capacity: PRIVATE_KEY_SIZE) let publicKey = UnsafeMutablePointer<UInt8>.allocate(capacity: PUBLIC_KEY_SIZE) ed25519_sha3_create_keypair(publicKey, privateKey, ConvertUtil.toNativeArray(privateKeySeed)) |
署名作成も同様。
1 2 3 4 5 6 7 8 9 10 11 |
static func sign(_ kv: KeyPair, _ message: [UInt8]) -> [UInt8] { let signature = UnsafeMutablePointer<UInt8>.allocate(capacity: 64) ed25519_sha3_sign(signature, ConvertUtil.toNativeArray(message), message.count, ConvertUtil.toNativeArray(kv.publicKey), ConvertUtil.toNativeArray(kv.privateKey)) return ConvertUtil.toArray(signature, 64) } |
以降の実装のロジックは Android版と同じですので、Android版の記事を見ていただくとわかるかと思います。
さいごに
今回はほぼ Ed25519 と SHA3-512 の話になってますが、実際のところ、Android も iOS もその部分が実装の一番の難所なんじゃないかと思います。
逆にその部分さえクリアしてしまえば、あとは普通の Web APIを使ったアプリを作るのと変わらないかと思いますので、本記事や Android版の記事をとっかかりに NEM のスマホアプリの作成も広まってくれれば幸いです。
Catapult は C++ で作ってるらしいので、暗号化回りのこの面倒な部分が共通化されてマルチプラットフォームで利用できるようになったりするかもしれません。そうだったらいいなぁ。
最後まで読んでいただきありがとうございます。 このブログを「いいな」と感じていただけましたら、Twiter にてフォローいただけるとうれしいです。ブログ更新情報などもお届けします。
Follow @ryuta461
この記事をシェアする