connect_swift_integration.ja.md
March 18, 2026 · View on GitHub
lang: ja direction: ltr source: docs/connect_swift_integration.md status: complete translator: manual
iOS プロジェクトでの NoritoBridgeKit 統合
このガイドでは、NoritoBridge.xcframework と Swift ラッパーを iOS アプリに組み込み、ConnectClient / ConnectSession / ConnectCrypto を利用して Rust と同じ Norito コーデックで Connect フレームを扱う手順を説明します。
前提条件
- NoritoBridge.xcframework(CI で生成)と
NoritoBridgeKit.swift(examples/ios/NoritoDemo/Sources/NoritoBridgeKit.swiftからコピー可能)。 - Xcode 15 以上、iOS 13 以上をターゲットとするプロジェクト。
Option A: Swift Package Manager(推奨)
crates/connect_norito_bridge/Package.swift.templateを使ってバイナリ SPM を公開(CI の URL / checksum を設定)。- Xcode →
File → Add Packages…→ リポジトリ URL を入力 →NoritoBridgeプロダクトをターゲットに追加。 NoritoBridgeKit.swiftをターゲットへ追加(ドラッグ&ドロップし、「Copy if needed」をオン)。
Option B: CocoaPods
NoritoBridge.podspec.templateから Podspec を作成し、s.sourceに zip URL を設定。pod trunk push NoritoBridge.podspec- Podfile に
pod 'NoritoBridge'を追加し、pod install NoritoBridgeKit.swiftをターゲットへ追加。
Imports
import Foundation
import CryptoKit // ChaChaPoly / HKDF
import IrohaSwift // ConnectClient / ConnectSession / ConnectCrypto
import NoritoBridge // XCFramework の Clang モジュール
// NoritoBridgeKit.swift も同じターゲットに含めてください
Connect セッションをブートストラップ
let connectURL = URL(string: "wss://node.example/v1/connect/ws?sid=\(sidB64)&role=app")!
var connectRequest = URLRequest(url: connectURL)
connectRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let connectClient = ConnectClient(request: connectRequest)
let sessionID = Data(base64Encoded: sidB64)!
Task {
await connectClient.start()
let keyPair = try ConnectCrypto.generateKeyPair()
var connectSession = ConnectSession(sessionID: sessionID, client: connectClient)
let open = ConnectOpen(
appPublicKey: keyPair.publicKey,
appMetadata: ConnectAppMetadata(name: "Demo dApp", iconURL: nil, description: nil),
constraints: ConnectConstraints(chainID: "00000000-0000-0000-0000-000000000000"),
permissions: ConnectPermissions(methods: ["sign"], events: [])
)
try await connectSession.sendOpen(open: open)
if case .approve(let approval) = try await connectSession.nextControlFrame() {
let directionKeys = try ConnectCrypto.deriveDirectionKeys(localPrivateKey: keyPair.privateKey,
peerPublicKey: approval.walletPublicKey,
sessionID: sessionID)
connectSession.setDirectionKeys(directionKeys)
let firstEnvelope = try await connectSession.nextEnvelope()
print("payload:", firstEnvelope.payload)
}
}
暗号化フレームの送信
let bridge = NoritoBridgeKit()
let seq = nextSequence()
let txBytes = Data([0x01, 0x02, 0x03])
let envelope = try bridge.encodeEnvelopeSignRequestTx(seq: seq, tx: txBytes)
let aad = ConnectAEAD.header(sessionID: sessionID, direction: .appToWallet, sequence: seq)
let nonce = ConnectAEAD.nonce(sequence: seq)
let ciphertext = try ChaChaPoly.seal(envelope,
using: SymmetricKey(data: directionKeys.appToWallet),
nonce: nonce,
authenticating: aad).combined
let frame = ConnectFrame(sessionID: sessionID,
direction: .appToWallet,
sequence: seq,
kind: .ciphertext(ConnectCiphertext(payload: ciphertext)))
try await connectClient.send(frame: frame)
ConnectAEAD(ヘッダー / nonce 生成ヘルパー)の例:
enum ConnectAEAD {
static func header(sessionID: Data, direction: ConnectDirection, sequence: UInt64) -> Data {
var buffer = Data()
buffer.append("connect:v1".data(using: .utf8)!)
buffer.append(sessionID)
buffer.append(direction == .appToWallet ? 0 : 1)
var seq = sequence.littleEndian
withUnsafeBytes(of: &seq) { buffer.append(contentsOf: \$0) }
buffer.append(1) // Ciphertext
return buffer
}
static func nonce(sequence: UInt64) -> ChaChaPoly.Nonce {
var bytes = Data(count: 12)
var seq = sequence.littleEndian
bytes.replaceSubrange(4..<12, with: withUnsafeBytes(of: &seq) { Data(\$0) })
return try! ChaChaPoly.Nonce(data: bytes)
}
}
受信と復号
func decryptFrame(_ frame: ConnectFrame,
symmetricKey: SymmetricKey,
sessionID: Data) throws -> ConnectEnvelope {
guard case .ciphertext(let payload) = frame.kind else {
throw ConnectEnvelopeError.unsupportedFrameKind
}
let aad = ConnectAEAD.header(sessionID: sessionID,
direction: frame.direction,
sequence: frame.sequence)
let nonce = ConnectAEAD.nonce(sequence: frame.sequence)
let box = try ChaChaPoly.SealedBox(combined: payload.payload)
let plaintext = try ChaChaPoly.open(box, using: symmetricKey, authenticating: aad)
return try ConnectEnvelope.decode(jsonData: plaintext)
}
NoritoBridgeKit.decodeCiphertextFrame や decodeEnvelopeJson なども用意されているため、デバッグや相互運用テストで直接ワイヤデータを確認できます。実運用では ConnectSession / ConnectEnvelope を使用し、Rust / Android SDK と完全に同じ挙動を維持することを推奨します。
CI バリデーション
-
ブリッジ成果物を更新したり Connect 統合を変更する前に、以下を実行してください。
make swift-ciこのターゲットはフィクスチャパリティの確認に加え、ダッシュボード用 JSON の検証と CLI レンダリングを行います。Buildkite では
ci/xcframework-smoke:<lane>:device_tag形式のメタデータに依存しており、対象レーン(iphone-simやstrongboxなど)を特定します。パイプラインやエージェントタグを変更した場合は、メタデータが出力されていることを必ず確認してください。 -
コマンドが失敗した場合は
docs/source/swift_parity_triage.mdの手順に従い、レンダリングされたmobile_ci出力を確認して、どのレーンに再生成またはインシデント対応が必要か判断してください。