From 7453b929c819fc926f8d805955988777467c879e Mon Sep 17 00:00:00 2001 From: Michael Bisgaard Olesen Date: Fri, 2 Feb 2024 14:57:57 +0100 Subject: [PATCH] Update Swift package for release Replace `Package.swift` to build the package from `./Sources` (generated Swift bridge code) and the binaries built from the previous commit and pushed as a "release" in the repo. This completes the migration of the repo from its former use as a simple package spec for the legacy `mobile_wallet` library to the new one which is embedded in the repo. Future releases are to follow the same two-step process layed out by this commit and its predecessor; i.e. two conscutive, tagged commits within a single PR. The complete process is described in the README. --- Package.swift | 22 +- README.md | 58 +- Sources/ConcordiumWalletCrypto/crypto.swift | 608 ++++++++++++++++++++ 3 files changed, 670 insertions(+), 18 deletions(-) create mode 100644 Sources/ConcordiumWalletCrypto/crypto.swift diff --git a/Package.swift b/Package.swift index d5e7308..3dafce8 100644 --- a/Package.swift +++ b/Package.swift @@ -1,16 +1,30 @@ -// swift-tools-version: 5.6 +// swift-tools-version:5.6 import PackageDescription let package = Package( name: "ConcordiumWalletCrypto", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + ], products: [ .library( name: "ConcordiumWalletCrypto", - targets: ["libmobile_wallet"]), + targets: ["ConcordiumWalletCrypto"] + ), ], - dependencies: [], targets: [ - .binaryTarget(name: "libmobile_wallet", url: "https://s3.eu-west-1.amazonaws.com/static-libraries.concordium.com/iOS/libmobile_wallet_0.24.0-0.xcframework.zip", checksum: "8051b9681901187537adc76858715d569d51abd4c201c376f292749da0b6eb60") + .binaryTarget( + name: "RustFramework", + url: "https://github.com/Concordium/concordium-wallet-crypto-swift/releases/download/build%2F1.0.0-1/RustFramework.xcframework.zip", + checksum: "edc2628d1721697b555891316dac3be1490072c1649d040fff8f3c160b2d0e09" + ), + .target( + name: "ConcordiumWalletCrypto", + dependencies: [ + .target(name: "RustFramework"), + ] + ), ] ) diff --git a/README.md b/README.md index 1b3c0f8..c451a15 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,60 @@ # ConcordiumWalletCrypto -[Swift package](https://developer.apple.com/documentation/xcode/swift-packages) providing Concordium's [Rust-based crypto library for mobile wallets](https://github.com/Concordium/concordium-base/tree/main/mobile_wallet), compiled for iOS as an [XCFramework](https://developer.apple.com/documentation/xcode/distributing-binary-frameworks-as-swift-packages). +[Swift package](https://developer.apple.com/documentation/xcode/swift-packages) providing bindings for Swift +of Concordium specific cryptographic functions that are written in Rust. -The binaries are compiled internally and uploaded to S3. The only target of the package is a binary target that references the appropriate version of this file. +The project is a core component of [`ConcordiumSwiftSdk`](https://github.com/Concordium/concordium-swift-sdk.git) +and only has its own repository because of limitations in SwiftPM +that prevent us from publishing everything in a single complete package. +In brief, Swift packages must be downloaded straight from git, +not from a registry to which we can publish the complete build. +This repository serves the dual purpose of hosting the Rust sources while simultaneously standing in as such a registry. -## Versioning +To avoid storing large binaries in git, a workflow compiles the bindings into an +[XCFramework](https://developer.apple.com/documentation/xcode/distributing-binary-frameworks-as-swift-packages) +and uploads it to GitHub as a release in this repository. -The available versions of the package are represented by the commit tags of this repository. +The package specification then refers to such a release, not the local Rust sources. +This means that the Swift sources (i.e. generated bridge code) and package spec made up by this repo +and its Rust sources aren't necessarily in sync. +The Swift files always refer to the latest published release, even during development of the next version. -The version follows the one of the [Rust sources](https://github.com/Concordium/concordium-base/blob/main/mobile_wallet/Cargo.toml) with a "build version" appended with a `-` separator. This extra component counts from 0 for any given library version and is bumped whenever a new Swift package based on the same Rust sources is built. +## Previous usage -## Relase new version +This repository/package was previously used to host the binaries built from a previous incarnation of the Rust library +which was built and hosted elsewhere. +See commit 6b6af29816b0f966598b170d62334e2faf00062b for details. -Steps for building and releasing a new version ``: +That package is still available from tag +[`0.24.0-0`](https://github.com/Concordium/concordium-wallet-crypto-swift/releases/tag/0.24.0-0) +and in use by the +[iOS reference wallet](https://github.com/Concordium/concordium-reference-wallet-ios/). -1. Using the appropriate automated job, run [`./build-ios.sh`](https://github.com/Concordium/concordium-base/blob/main/mobile_wallet/scripts/build-ios.sh) from `concordium-base`, archive `ios/build/libmobile_wallet.xcframework` as a compressed zip file named `libmobile_wallet_.xcframework.zip`, and upload this file to S3 bucket/path `static-libraries.concordium.com/iOS/`. +## Versioning -2. Compute checksum of the archive using the command - ```shell - swift package compute-checksum libmobile_wallet_.xcframework.zip - ``` +The available versions of the package are represented by the commit tags of this repository. +The version is defined by the Rust/Cargo project. + +## Release new version -3. Commit a change to [`Package.swift`](https://github.com/Concordium/concordium-wallet-crypto-swift/blob/main/Package.swift), updating the `libmobile_wallet` binary target with the new version in the `url` string and the new `checksum`. +The Rust code is developed as usually without making changes to `./Sources` nor `Package.swift`. -4. Push an annotated tag named by the version for the new commit: +Once it's ready for a new release, the steps for building and releasing a new version `` are: + +1. Create and push a tag named `build/-`, + where `` is bumped (starting from 0) for each attempt at building ``. + This is necessary because GitHub requires releases to be tagged and the following workflow uploads a release. +2. Run the [workflow](./.github/workflows/publish-bindings.yml) for publishing a new version of the binary framework. + Use the tag you just created as "branch" to run from and input `` (i.e. without the counter for "Version"). +3. Run [`./build.sh`](./build.sh) locally to regenerate the Swift bridge sources. + [TODO: Running the "bindgen" step is sufficient.] +4. Update `Package.swift` with the updated `url` and `checksum` of the binary framework. + The workflow prints the checksum as the last step of its execution. +5. Commit the changes and push an annotated tag named by the version for the new commit: ```shell git tag -a ``` Give the tag a message describing what changed in the new version. + +The entire process should be done as a single PR which, +in order to preserve the tags, gets merged without squashing. diff --git a/Sources/ConcordiumWalletCrypto/crypto.swift b/Sources/ConcordiumWalletCrypto/crypto.swift new file mode 100644 index 0000000..a07e1bf --- /dev/null +++ b/Sources/ConcordiumWalletCrypto/crypto.swift @@ -0,0 +1,608 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +import Foundation + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport(cryptoFFI) + import cryptoFFI +#endif + +private extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func from(_ ptr: UnsafeBufferPointer) -> RustBuffer { + try! rustCall { ffi_crypto_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { ffi_crypto_rustbuffer_free(self, $0) } + } +} + +private extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + +// Helper classes/extensions that don't change. +// Someday, this will be in a library of its own. + +private extension Data { + init(rustBuffer: RustBuffer) { + // TODO: This copies the buffer. Can we read directly from a + // Rust buffer? + self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len)) + } +} + +// Define reader functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. +// +// With external types, one swift source file needs to be able to call the read +// method on another source file's FfiConverter, but then what visibility +// should Reader have? +// - If Reader is fileprivate, then this means the read() must also +// be fileprivate, which doesn't work with external types. +// - If Reader is internal/public, we'll get compile errors since both source +// files will try define the same type. +// +// Instead, the read() method and these helper functions input a tuple of data + +private func createReader(data: Data) -> (data: Data, offset: Data.Index) { + (data: data, offset: 0) +} + +// Reads an integer at the current offset, in big-endian order, and advances +// the offset on success. Throws if reading the integer would move the +// offset past the end of the buffer. +private func readInt(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset ..< reader.offset + MemoryLayout.size + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = reader.data[reader.offset] + reader.offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value) { reader.data.copyBytes(to: $0, from: range) } + reader.offset = range.upperBound + return value.bigEndian +} + +// Reads an arbitrary number of bytes, to be used to read +// raw bytes, this is useful when lifting strings +private func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> [UInt8] { + let range = reader.offset ..< (reader.offset + count) + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer { buffer in + reader.data.copyBytes(to: buffer, from: range) + } + reader.offset = range.upperBound + return value +} + +// Reads a float at the current offset. +private func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float { + return try Float(bitPattern: readInt(&reader)) +} + +// Reads a float at the current offset. +private func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double { + return try Double(bitPattern: readInt(&reader)) +} + +// Indicates if the offset has reached the end of the buffer. +private func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool { + return reader.offset < reader.data.count +} + +// Define writer functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. See the above discussion on Readers for details. + +private func createWriter() -> [UInt8] { + return [] +} + +private func writeBytes(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 { + writer.append(contentsOf: byteArr) +} + +// Writes an integer in big-endian order. +// +// Warning: make sure what you are trying to write +// is in the correct type! +private func writeInt(_ writer: inout [UInt8], _ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) } +} + +private func writeFloat(_ writer: inout [UInt8], _ value: Float) { + writeInt(&writer, value.bitPattern) +} + +private func writeDouble(_ writer: inout [UInt8], _ value: Double) { + writeInt(&writer, value.bitPattern) +} + +// Protocol for types that transfer other types across the FFI. This is +// analogous go the Rust trait of the same name. +private protocol FfiConverter { + associatedtype FfiType + associatedtype SwiftType + + static func lift(_ value: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType + static func write(_ value: SwiftType, into buf: inout [UInt8]) +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +private protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType {} + +extension FfiConverterPrimitive { + public static func lift(_ value: FfiType) throws -> SwiftType { + return value + } + + public static func lower(_ value: SwiftType) -> FfiType { + return value + } +} + +// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`. +// Used for complex types where it's hard to write a custom lift/lower. +private protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} + +extension FfiConverterRustBuffer { + public static func lift(_ buf: RustBuffer) throws -> SwiftType { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + + public static func lower(_ value: SwiftType) -> RustBuffer { + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) + } +} + +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +private enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +private let CALL_SUCCESS: Int8 = 0 +private let CALL_ERROR: Int8 = 1 +private let CALL_PANIC: Int8 = 2 +private let CALL_CANCELLED: Int8 = 3 + +private extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall(_ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: nil) +} + +private func rustCallWithError( + _ errorHandler: @escaping (RustBuffer) throws -> Error, + _ callback: (UnsafeMutablePointer) -> T +) throws -> T { + try makeRustCall(callback, errorHandler: errorHandler) +} + +private func makeRustCall( + _ callback: (UnsafeMutablePointer) -> T, + errorHandler: ((RustBuffer) throws -> Error)? +) throws -> T { + uniffiEnsureInitialized() + var callStatus = RustCallStatus() + let returnedVal = callback(&callStatus) + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler) + return returnedVal +} + +private func uniffiCheckCallStatus( + callStatus: RustCallStatus, + errorHandler: ((RustBuffer) throws -> Error)? +) throws { + switch callStatus.code { + case CALL_SUCCESS: + return + + case CALL_ERROR: + if let errorHandler = errorHandler { + throw try errorHandler(callStatus.errorBuf) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.unexpectedRustCallError + } + + case CALL_PANIC: + // When the rust code sees a panic, it tries to construct a RustBuffer + // with the message. But if that code panics, then it just sends back + // an empty buffer. + if callStatus.errorBuf.len > 0 { + throw try UniffiInternalError.rustPanic(FfiConverterString.lift(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + case CALL_CANCELLED: + throw CancellationError() + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} + +// Public interface members begin here. + +private struct FfiConverterUInt8: FfiConverterPrimitive { + typealias FfiType = UInt8 + typealias SwiftType = UInt8 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: UInt8, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +private struct FfiConverterUInt32: FfiConverterPrimitive { + typealias FfiType = UInt32 + typealias SwiftType = UInt32 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +private struct FfiConverterUInt64: FfiConverterPrimitive { + typealias FfiType = UInt64 + typealias SwiftType = UInt64 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt64 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +private struct FfiConverterString: FfiConverter { + typealias SwiftType = String + typealias FfiType = RustBuffer + + public static func lift(_ value: RustBuffer) throws -> String { + defer { + value.deallocate() + } + if value.data == nil { + return String() + } + let bytes = UnsafeBufferPointer(start: value.data!, count: Int(value.len)) + return String(bytes: bytes, encoding: String.Encoding.utf8)! + } + + public static func lower(_ value: String) -> RustBuffer { + return value.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + let len: Int32 = try readInt(&buf) + return try String(bytes: readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)! + } + + public static func write(_ value: String, into buf: inout [UInt8]) { + let len = Int32(value.utf8.count) + writeInt(&buf, len) + writeBytes(&buf, value.utf8) + } +} + +public enum ConcordiumWalletCryptoError { + // Simple error enums only carry a message + case CallFailed(message: String) + + fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { + return try FfiConverterTypeConcordiumWalletCryptoError.lift(error) + } +} + +public struct FfiConverterTypeConcordiumWalletCryptoError: FfiConverterRustBuffer { + typealias SwiftType = ConcordiumWalletCryptoError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ConcordiumWalletCryptoError { + let variant: Int32 = try readInt(&buf) + switch variant { + case 1: return try .CallFailed( + message: FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: ConcordiumWalletCryptoError, into buf: inout [UInt8]) { + switch value { + case .CallFailed(_ /* message is ignored*/ ): + writeInt(&buf, Int32(1)) + } + } +} + +extension ConcordiumWalletCryptoError: Equatable, Hashable {} + +extension ConcordiumWalletCryptoError: Error {} + +public func getAccountPublicKey(seedHex: String, network: String, identityProviderIndex: UInt32, identityIndex: UInt32, credentialCounter: UInt32) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_account_public_key( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), + FfiConverterUInt32.lower(identityProviderIndex), + FfiConverterUInt32.lower(identityIndex), + FfiConverterUInt32.lower(credentialCounter), $0 + ) + } + ) +} + +public func getAccountSigningKey(seedHex: String, network: String, identityProviderIndex: UInt32, identityIndex: UInt32, credentialCounter: UInt32) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_account_signing_key( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), + FfiConverterUInt32.lower(identityProviderIndex), + FfiConverterUInt32.lower(identityIndex), + FfiConverterUInt32.lower(credentialCounter), $0 + ) + } + ) +} + +public func getAttributeCommitmentRandomness(seedHex: String, network: String, identityProviderIndex: UInt32, identityIndex: UInt32, credentialCounter: UInt32, attribute: UInt8) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_attribute_commitment_randomness( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), + FfiConverterUInt32.lower(identityProviderIndex), + FfiConverterUInt32.lower(identityIndex), + FfiConverterUInt32.lower(credentialCounter), + FfiConverterUInt8.lower(attribute), $0 + ) + } + ) +} + +public func getCredentialId(seedHex: String, network: String, identityProviderIndex: UInt32, identityIndex: UInt32, credentialCounter: UInt8, commitmentKey: String) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_credential_id( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), + FfiConverterUInt32.lower(identityProviderIndex), + FfiConverterUInt32.lower(identityIndex), + FfiConverterUInt8.lower(credentialCounter), + FfiConverterString.lower(commitmentKey), $0 + ) + } + ) +} + +public func getIdCredSec(seedHex: String, network: String, identityProviderIndex: UInt32, identityIndex: UInt32) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_id_cred_sec( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), + FfiConverterUInt32.lower(identityProviderIndex), + FfiConverterUInt32.lower(identityIndex), $0 + ) + } + ) +} + +public func getPrfKey(seedHex: String, network: String, identityProviderIndex: UInt32, identityIndex: UInt32) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_prf_key( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), + FfiConverterUInt32.lower(identityProviderIndex), + FfiConverterUInt32.lower(identityIndex), $0 + ) + } + ) +} + +public func getSignatureBlindingRandomness(seedHex: String, network: String, identityProviderIndex: UInt32, identityIndex: UInt32) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_signature_blinding_randomness( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), + FfiConverterUInt32.lower(identityProviderIndex), + FfiConverterUInt32.lower(identityIndex), $0 + ) + } + ) +} + +public func getVerifiableCredentialBackupEncryptionKey(seedHex: String, network: String) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_verifiable_credential_backup_encryption_key( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), $0 + ) + } + ) +} + +public func getVerifiableCredentialPublicKey(seedHex: String, network: String, issuerIndex: UInt64, issuerSubindex: UInt64, verifiableCredentialIndex: UInt32) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_verifiable_credential_public_key( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), + FfiConverterUInt64.lower(issuerIndex), + FfiConverterUInt64.lower(issuerSubindex), + FfiConverterUInt32.lower(verifiableCredentialIndex), $0 + ) + } + ) +} + +public func getVerifiableCredentialSigningKey(seedHex: String, network: String, issuerIndex: UInt64, issuerSubindex: UInt64, verifiableCredentialIndex: UInt32) throws -> String { + return try FfiConverterString.lift( + rustCallWithError(FfiConverterTypeConcordiumWalletCryptoError.lift) { + uniffi_crypto_fn_func_get_verifiable_credential_signing_key( + FfiConverterString.lower(seedHex), + FfiConverterString.lower(network), + FfiConverterUInt64.lower(issuerIndex), + FfiConverterUInt64.lower(issuerSubindex), + FfiConverterUInt32.lower(verifiableCredentialIndex), $0 + ) + } + ) +} + +private enum InitializationResult { + case ok + case contractVersionMismatch + case apiChecksumMismatch +} + +// Use a global variables to perform the versioning checks. Swift ensures that +// the code inside is only computed once. +private var initializationResult: InitializationResult { + // Get the bindings contract version from our ComponentInterface + let bindings_contract_version = 24 + // Get the scaffolding contract version by calling the into the dylib + let scaffolding_contract_version = ffi_crypto_uniffi_contract_version() + if bindings_contract_version != scaffolding_contract_version { + return InitializationResult.contractVersionMismatch + } + if uniffi_crypto_checksum_func_get_account_public_key() != 42526 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_crypto_checksum_func_get_account_signing_key() != 11644 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_crypto_checksum_func_get_attribute_commitment_randomness() != 35542 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_crypto_checksum_func_get_credential_id() != 21427 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_crypto_checksum_func_get_id_cred_sec() != 31706 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_crypto_checksum_func_get_prf_key() != 50598 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_crypto_checksum_func_get_signature_blinding_randomness() != 63616 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_crypto_checksum_func_get_verifiable_credential_backup_encryption_key() != 16052 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_crypto_checksum_func_get_verifiable_credential_public_key() != 65074 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_crypto_checksum_func_get_verifiable_credential_signing_key() != 36134 { + return InitializationResult.apiChecksumMismatch + } + + return InitializationResult.ok +} + +private func uniffiEnsureInitialized() { + switch initializationResult { + case .ok: + break + case .contractVersionMismatch: + fatalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + case .apiChecksumMismatch: + fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +}