diff --git a/CHANGELOG.md b/CHANGELOG.md index 9577b40..27b5a38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Add a `wake` module for the wake push gateway: device key, secret and install id generation, a signed registration (`wake_prepare_registration`, `wake_sign_pubky_proof`, `wake_register`), decryption of legacy (v0) and v1 envelopes from any delivered push (`wake_decrypt_push`), and the device routes for acks, presence, topics and unregistering. The v1 wire format is experimental until the apps adopt it. `register_device` and `test_notification` are unchanged. + ## 0.5.16 - 2026-09-14 - Serialize Jade discovery, connection setup, and teardown. A disconnect now cancels connection attempts that are in progress or queued, and a transport opened by a cancelled attempt is closed instead of leaked. A scan started while another lifecycle operation is running returns `DeviceBusy`. diff --git a/Cargo.lock b/Cargo.lock index e3347fd..ec833f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -987,6 +987,7 @@ dependencies = [ name = "bitkitcore" version = "0.6.0" dependencies = [ + "aes-gcm", "alloy-primitives", "alloy-rlp", "alloy-sol-types", diff --git a/Cargo.toml b/Cargo.toml index e02db8b..5675677 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" [dependencies] uniffi = { version = "0.29.4", features = [ "cli", "bindgen", "tokio" ] } -serde_json = "1.0.114" +serde_json = { version = "1.0.114", features = ["raw_value"] } serde = { version = "^1.0.209", features = ["derive"] } tokio = { version = "1.40.0", features = ["rt", "rt-multi-thread", "macros", "time", "sync"] } bitcoin = "0.32.4" @@ -40,6 +40,7 @@ bip39 = { version = "2.0", features = ["zeroize"] } bdk = { version = "0.30.2", features = ["all-keys"] } boltz-client = { version = "0.4.1", default-features = false, features = ["electrum", "ws"] } base64 = "0.22" +aes-gcm = "0.10.3" minicbor = { version = "2", features = ["alloc"] } ur = "0.5.2" log = "0.4" diff --git a/bindings/ios/bitkitcore.swift b/bindings/ios/bitkitcore.swift index 6cc0019..e26cc94 100644 --- a/bindings/ios/bitkitcore.swift +++ b/bindings/ios/bitkitcore.swift @@ -403,6 +403,22 @@ private let UNIFFI_CALLBACK_SUCCESS: Int32 = 0 private let UNIFFI_CALLBACK_ERROR: Int32 = 1 private let UNIFFI_CALLBACK_UNEXPECTED_ERROR: Int32 = 2 +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate 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)) + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -16364,129 +16380,77 @@ public func FfiConverterTypeValidationResult_lower(_ value: ValidationResult) -> /** - * Balance breakdown from BDK. + * The bearer credential of the device routes, generated on the device. */ -public struct WalletBalance { - /** - * Confirmed and spendable balance (sats) - */ - public var confirmed: UInt64 - /** - * Immature coinbase outputs (sats) - */ - public var immature: UInt64 - /** - * Unconfirmed UTXOs from trusted sources (own change) (sats) - */ - public var trustedPending: UInt64 - /** - * Unconfirmed UTXOs from external sources (sats) - */ - public var untrustedPending: UInt64 +public struct WakeDeviceSecret { /** - * Total spendable: confirmed + trusted_pending (sats) + * `wkd_` followed by 43 base64url characters. Keep it in secure storage + * the notification extension can read. */ - public var spendable: UInt64 + public var token: String /** - * Grand total: all categories (sats) + * Lowercase hex SHA-256 of `token`, sent as `secret_sha256` when + * registering. The gateway stores only this hash. */ - public var total: UInt64 + public var sha256Hex: String // Default memberwise initializers are never public by default, so we // declare one manually. public init( /** - * Confirmed and spendable balance (sats) - */confirmed: UInt64, - /** - * Immature coinbase outputs (sats) - */immature: UInt64, - /** - * Unconfirmed UTXOs from trusted sources (own change) (sats) - */trustedPending: UInt64, - /** - * Unconfirmed UTXOs from external sources (sats) - */untrustedPending: UInt64, - /** - * Total spendable: confirmed + trusted_pending (sats) - */spendable: UInt64, + * `wkd_` followed by 43 base64url characters. Keep it in secure storage + * the notification extension can read. + */token: String, /** - * Grand total: all categories (sats) - */total: UInt64) { - self.confirmed = confirmed - self.immature = immature - self.trustedPending = trustedPending - self.untrustedPending = untrustedPending - self.spendable = spendable - self.total = total + * Lowercase hex SHA-256 of `token`, sent as `secret_sha256` when + * registering. The gateway stores only this hash. + */sha256Hex: String) { + self.token = token + self.sha256Hex = sha256Hex } } #if compiler(>=6) -extension WalletBalance: Sendable {} +extension WakeDeviceSecret: Sendable {} #endif -extension WalletBalance: Equatable, Hashable { - public static func ==(lhs: WalletBalance, rhs: WalletBalance) -> Bool { - if lhs.confirmed != rhs.confirmed { - return false - } - if lhs.immature != rhs.immature { - return false - } - if lhs.trustedPending != rhs.trustedPending { - return false - } - if lhs.untrustedPending != rhs.untrustedPending { +extension WakeDeviceSecret: Equatable, Hashable { + public static func ==(lhs: WakeDeviceSecret, rhs: WakeDeviceSecret) -> Bool { + if lhs.token != rhs.token { return false } - if lhs.spendable != rhs.spendable { - return false - } - if lhs.total != rhs.total { + if lhs.sha256Hex != rhs.sha256Hex { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(confirmed) - hasher.combine(immature) - hasher.combine(trustedPending) - hasher.combine(untrustedPending) - hasher.combine(spendable) - hasher.combine(total) + hasher.combine(token) + hasher.combine(sha256Hex) } } -extension WalletBalance: Codable {} +extension WakeDeviceSecret: Codable {} #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeWalletBalance: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WalletBalance { +public struct FfiConverterTypeWakeDeviceSecret: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeDeviceSecret { return - try WalletBalance( - confirmed: FfiConverterUInt64.read(from: &buf), - immature: FfiConverterUInt64.read(from: &buf), - trustedPending: FfiConverterUInt64.read(from: &buf), - untrustedPending: FfiConverterUInt64.read(from: &buf), - spendable: FfiConverterUInt64.read(from: &buf), - total: FfiConverterUInt64.read(from: &buf) + try WakeDeviceSecret( + token: FfiConverterString.read(from: &buf), + sha256Hex: FfiConverterString.read(from: &buf) ) } - public static func write(_ value: WalletBalance, into buf: inout [UInt8]) { - FfiConverterUInt64.write(value.confirmed, into: &buf) - FfiConverterUInt64.write(value.immature, into: &buf) - FfiConverterUInt64.write(value.trustedPending, into: &buf) - FfiConverterUInt64.write(value.untrustedPending, into: &buf) - FfiConverterUInt64.write(value.spendable, into: &buf) - FfiConverterUInt64.write(value.total, into: &buf) + public static func write(_ value: WakeDeviceSecret, into buf: inout [UInt8]) { + FfiConverterString.write(value.token, into: &buf) + FfiConverterString.write(value.sha256Hex, into: &buf) } } @@ -16494,128 +16458,170 @@ public struct FfiConverterTypeWalletBalance: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeWalletBalance_lift(_ buf: RustBuffer) throws -> WalletBalance { - return try FfiConverterTypeWalletBalance.lift(buf) +public func FfiConverterTypeWakeDeviceSecret_lift(_ buf: RustBuffer) throws -> WakeDeviceSecret { + return try FfiConverterTypeWakeDeviceSecret.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeWalletBalance_lower(_ value: WalletBalance) -> RustBuffer { - return FfiConverterTypeWalletBalance.lower(value) +public func FfiConverterTypeWakeDeviceSecret_lower(_ value: WakeDeviceSecret) -> RustBuffer { + return FfiConverterTypeWakeDeviceSecret.lower(value) } /** - * Common parameters for creating and syncing a watch-only BDK wallet. + * A wake found in a push. */ -public struct WalletParams { +public struct WakeEnvelope { /** - * Extended public key (xpub/ypub/zpub/tpub/upub/vpub) + * 0 for the legacy envelope, 1 for wake v1. */ - public var extendedKey: String + public var version: UInt8 /** - * Electrum server URL for wallet sync + * The wake id to acknowledge (v1 and fallback only). */ - public var electrumUrl: String + public var id: String? /** - * Root fingerprint hex (e.g. "73c5da0a"). Required for hardware wallet signing. + * The topic; `source.type` for legacy envelopes. */ - public var fingerprint: String? + public var topic: String /** - * Bitcoin network (auto-detected from key prefix if not specified) + * `background`, `alert` or `time_sensitive` (v1 only). */ - public var network: Network? + public var urgency: String? /** - * Override account type for ambiguous key prefixes (xpub/tpub) + * Unix seconds after which the wake is stale (v1 only). */ - public var accountType: AccountType? + public var deadline: UInt64? + /** + * The producer payload as raw JSON, when there is one. + */ + public var payloadJson: String? + /** + * `createdAt` of a legacy envelope. + */ + public var createdAt: String? + /** + * True for a fallback alert, which carries nothing to decrypt. + */ + public var fallback: Bool // Default memberwise initializers are never public by default, so we // declare one manually. public init( /** - * Extended public key (xpub/ypub/zpub/tpub/upub/vpub) - */extendedKey: String, + * 0 for the legacy envelope, 1 for wake v1. + */version: UInt8, /** - * Electrum server URL for wallet sync - */electrumUrl: String, + * The wake id to acknowledge (v1 and fallback only). + */id: String?, /** - * Root fingerprint hex (e.g. "73c5da0a"). Required for hardware wallet signing. - */fingerprint: String?, + * The topic; `source.type` for legacy envelopes. + */topic: String, /** - * Bitcoin network (auto-detected from key prefix if not specified) - */network: Network?, + * `background`, `alert` or `time_sensitive` (v1 only). + */urgency: String?, /** - * Override account type for ambiguous key prefixes (xpub/tpub) - */accountType: AccountType?) { - self.extendedKey = extendedKey - self.electrumUrl = electrumUrl - self.fingerprint = fingerprint - self.network = network - self.accountType = accountType + * Unix seconds after which the wake is stale (v1 only). + */deadline: UInt64?, + /** + * The producer payload as raw JSON, when there is one. + */payloadJson: String?, + /** + * `createdAt` of a legacy envelope. + */createdAt: String?, + /** + * True for a fallback alert, which carries nothing to decrypt. + */fallback: Bool) { + self.version = version + self.id = id + self.topic = topic + self.urgency = urgency + self.deadline = deadline + self.payloadJson = payloadJson + self.createdAt = createdAt + self.fallback = fallback } } #if compiler(>=6) -extension WalletParams: Sendable {} +extension WakeEnvelope: Sendable {} #endif -extension WalletParams: Equatable, Hashable { - public static func ==(lhs: WalletParams, rhs: WalletParams) -> Bool { - if lhs.extendedKey != rhs.extendedKey { +extension WakeEnvelope: Equatable, Hashable { + public static func ==(lhs: WakeEnvelope, rhs: WakeEnvelope) -> Bool { + if lhs.version != rhs.version { return false } - if lhs.electrumUrl != rhs.electrumUrl { + if lhs.id != rhs.id { return false } - if lhs.fingerprint != rhs.fingerprint { + if lhs.topic != rhs.topic { return false } - if lhs.network != rhs.network { + if lhs.urgency != rhs.urgency { return false } - if lhs.accountType != rhs.accountType { + if lhs.deadline != rhs.deadline { + return false + } + if lhs.payloadJson != rhs.payloadJson { + return false + } + if lhs.createdAt != rhs.createdAt { + return false + } + if lhs.fallback != rhs.fallback { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(extendedKey) - hasher.combine(electrumUrl) - hasher.combine(fingerprint) - hasher.combine(network) - hasher.combine(accountType) + hasher.combine(version) + hasher.combine(id) + hasher.combine(topic) + hasher.combine(urgency) + hasher.combine(deadline) + hasher.combine(payloadJson) + hasher.combine(createdAt) + hasher.combine(fallback) } } -extension WalletParams: Codable {} +extension WakeEnvelope: Codable {} #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeWalletParams: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WalletParams { +public struct FfiConverterTypeWakeEnvelope: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeEnvelope { return - try WalletParams( - extendedKey: FfiConverterString.read(from: &buf), - electrumUrl: FfiConverterString.read(from: &buf), - fingerprint: FfiConverterOptionString.read(from: &buf), - network: FfiConverterOptionTypeNetwork.read(from: &buf), - accountType: FfiConverterOptionTypeAccountType.read(from: &buf) + try WakeEnvelope( + version: FfiConverterUInt8.read(from: &buf), + id: FfiConverterOptionString.read(from: &buf), + topic: FfiConverterString.read(from: &buf), + urgency: FfiConverterOptionString.read(from: &buf), + deadline: FfiConverterOptionUInt64.read(from: &buf), + payloadJson: FfiConverterOptionString.read(from: &buf), + createdAt: FfiConverterOptionString.read(from: &buf), + fallback: FfiConverterBool.read(from: &buf) ) } - public static func write(_ value: WalletParams, into buf: inout [UInt8]) { - FfiConverterString.write(value.extendedKey, into: &buf) - FfiConverterString.write(value.electrumUrl, into: &buf) - FfiConverterOptionString.write(value.fingerprint, into: &buf) - FfiConverterOptionTypeNetwork.write(value.network, into: &buf) - FfiConverterOptionTypeAccountType.write(value.accountType, into: &buf) + public static func write(_ value: WakeEnvelope, into buf: inout [UInt8]) { + FfiConverterUInt8.write(value.version, into: &buf) + FfiConverterOptionString.write(value.id, into: &buf) + FfiConverterString.write(value.topic, into: &buf) + FfiConverterOptionString.write(value.urgency, into: &buf) + FfiConverterOptionUInt64.write(value.deadline, into: &buf) + FfiConverterOptionString.write(value.payloadJson, into: &buf) + FfiConverterOptionString.write(value.createdAt, into: &buf) + FfiConverterBool.write(value.fallback, into: &buf) } } @@ -16623,160 +16629,80 @@ public struct FfiConverterTypeWalletParams: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeWalletParams_lift(_ buf: RustBuffer) throws -> WalletParams { - return try FfiConverterTypeWalletParams.lift(buf) +public func FfiConverterTypeWakeEnvelope_lift(_ buf: RustBuffer) throws -> WakeEnvelope { + return try FfiConverterTypeWakeEnvelope.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeWalletParams_lower(_ value: WalletParams) -> RustBuffer { - return FfiConverterTypeWalletParams.lower(value) +public func FfiConverterTypeWakeEnvelope_lower(_ value: WakeEnvelope) -> RustBuffer { + return FfiConverterTypeWakeEnvelope.lower(value) } /** - * Parameters for starting an xpub transaction watcher. + * One identity's signature over `WakeRegistrationRequest.message`. */ -public struct WatcherParams { - /** - * Caller-supplied identifier for this watcher. - */ - public var watcherId: String - /** - * Wallet id that scopes the activities this watcher emits. Apps may use - * one wallet id for several account watchers and merge their snapshots. - */ - public var walletId: String - /** - * Extended public key (xpub/ypub/zpub/tpub/upub/vpub). - */ - public var extendedKey: String - /** - * Electrum server URL (e.g. "ssl://electrum.example.com:50002"). - */ - public var electrumUrl: String - /** - * Bitcoin network override (auto-detected from key prefix if None). - */ - public var network: Network? +public struct WakeIdentityProof { + public var identity: String /** - * Account type override (auto-detected from key prefix if None). - */ - public var accountType: AccountType? - /** - * Number of unused addresses to monitor beyond the last used - * (defaults to `DEFAULT_GAP_LIMIT` when None). + * `ln:`: 104 zbase32 characters. `pk:`: 128 lowercase hex characters. */ - public var gapLimit: UInt32? + public var signature: String // Default memberwise initializers are never public by default, so we // declare one manually. - public init( - /** - * Caller-supplied identifier for this watcher. - */watcherId: String, - /** - * Wallet id that scopes the activities this watcher emits. Apps may use - * one wallet id for several account watchers and merge their snapshots. - */walletId: String, + public init(identity: String, /** - * Extended public key (xpub/ypub/zpub/tpub/upub/vpub). - */extendedKey: String, - /** - * Electrum server URL (e.g. "ssl://electrum.example.com:50002"). - */electrumUrl: String, - /** - * Bitcoin network override (auto-detected from key prefix if None). - */network: Network?, - /** - * Account type override (auto-detected from key prefix if None). - */accountType: AccountType?, - /** - * Number of unused addresses to monitor beyond the last used - * (defaults to `DEFAULT_GAP_LIMIT` when None). - */gapLimit: UInt32?) { - self.watcherId = watcherId - self.walletId = walletId - self.extendedKey = extendedKey - self.electrumUrl = electrumUrl - self.network = network - self.accountType = accountType - self.gapLimit = gapLimit + * `ln:`: 104 zbase32 characters. `pk:`: 128 lowercase hex characters. + */signature: String) { + self.identity = identity + self.signature = signature } } #if compiler(>=6) -extension WatcherParams: Sendable {} +extension WakeIdentityProof: Sendable {} #endif -extension WatcherParams: Equatable, Hashable { - public static func ==(lhs: WatcherParams, rhs: WatcherParams) -> Bool { - if lhs.watcherId != rhs.watcherId { +extension WakeIdentityProof: Equatable, Hashable { + public static func ==(lhs: WakeIdentityProof, rhs: WakeIdentityProof) -> Bool { + if lhs.identity != rhs.identity { return false } - if lhs.walletId != rhs.walletId { - return false - } - if lhs.extendedKey != rhs.extendedKey { - return false - } - if lhs.electrumUrl != rhs.electrumUrl { - return false - } - if lhs.network != rhs.network { - return false - } - if lhs.accountType != rhs.accountType { - return false - } - if lhs.gapLimit != rhs.gapLimit { + if lhs.signature != rhs.signature { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(watcherId) - hasher.combine(walletId) - hasher.combine(extendedKey) - hasher.combine(electrumUrl) - hasher.combine(network) - hasher.combine(accountType) - hasher.combine(gapLimit) + hasher.combine(identity) + hasher.combine(signature) } } -extension WatcherParams: Codable {} +extension WakeIdentityProof: Codable {} #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeWatcherParams: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WatcherParams { +public struct FfiConverterTypeWakeIdentityProof: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeIdentityProof { return - try WatcherParams( - watcherId: FfiConverterString.read(from: &buf), - walletId: FfiConverterString.read(from: &buf), - extendedKey: FfiConverterString.read(from: &buf), - electrumUrl: FfiConverterString.read(from: &buf), - network: FfiConverterOptionTypeNetwork.read(from: &buf), - accountType: FfiConverterOptionTypeAccountType.read(from: &buf), - gapLimit: FfiConverterOptionUInt32.read(from: &buf) + try WakeIdentityProof( + identity: FfiConverterString.read(from: &buf), + signature: FfiConverterString.read(from: &buf) ) } - public static func write(_ value: WatcherParams, into buf: inout [UInt8]) { - FfiConverterString.write(value.watcherId, into: &buf) - FfiConverterString.write(value.walletId, into: &buf) - FfiConverterString.write(value.extendedKey, into: &buf) - FfiConverterString.write(value.electrumUrl, into: &buf) - FfiConverterOptionTypeNetwork.write(value.network, into: &buf) - FfiConverterOptionTypeAccountType.write(value.accountType, into: &buf) - FfiConverterOptionUInt32.write(value.gapLimit, into: &buf) + public static func write(_ value: WakeIdentityProof, into buf: inout [UInt8]) { + FfiConverterString.write(value.identity, into: &buf) + FfiConverterString.write(value.signature, into: &buf) } } @@ -16784,371 +16710,397 @@ public struct FfiConverterTypeWatcherParams: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeWatcherParams_lift(_ buf: RustBuffer) throws -> WatcherParams { - return try FfiConverterTypeWatcherParams.lift(buf) +public func FfiConverterTypeWakeIdentityProof_lift(_ buf: RustBuffer) throws -> WakeIdentityProof { + return try FfiConverterTypeWakeIdentityProof.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeWatcherParams_lower(_ value: WatcherParams) -> RustBuffer { - return FfiConverterTypeWatcherParams.lower(value) +public func FfiConverterTypeWakeIdentityProof_lower(_ value: WakeIdentityProof) -> RustBuffer { + return FfiConverterTypeWakeIdentityProof.lower(value) } /** - * Errors specific to account info operations (BDK/Electrum-based). + * A secp256k1 key pair for push encryption. The public key is registered + * with the gateway; the secret key decrypts wakes and never leaves the device. */ -public enum AccountInfoError: Swift.Error { - - - - /** - * The provided extended public key is invalid or cannot be parsed - */ - case InvalidExtendedKey(errorDetails: String - ) - /** - * The provided address is invalid - */ - case InvalidAddress(errorDetails: String - ) - /** - * Electrum connection or query failed - */ - case ElectrumError(errorDetails: String - ) - /** - * BDK wallet creation or operation error - */ - case WalletError(errorDetails: String - ) - /** - * Wallet sync with Electrum failed - */ - case SyncError(errorDetails: String - ) - /** - * The key type/prefix is not recognized - */ - case UnsupportedKeyType(errorDetails: String - ) - /** - * Network mismatch between key prefix and specified network - */ - case NetworkMismatch(errorDetails: String - ) - /** - * Invalid transaction ID provided - */ - case InvalidTxid(errorDetails: String - ) +public struct WakeKeyPair { /** - * A valid transaction ID was not found in the wallet + * 64 lowercase hex characters. */ - case TransactionNotFound(errorDetails: String - ) + public var secretKeyHex: String /** - * Watcher lifecycle or subscription error + * Compressed public key, 66 lowercase hex characters. */ - case WatcherError(errorDetails: String - ) -} + public var publicKeyHex: String + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * 64 lowercase hex characters. + */secretKeyHex: String, + /** + * Compressed public key, 66 lowercase hex characters. + */publicKeyHex: String) { + self.secretKeyHex = secretKeyHex + self.publicKeyHex = publicKeyHex + } +} -#if swift(>=5.8) -@_documentation(visibility: private) +#if compiler(>=6) +extension WakeKeyPair: Sendable {} #endif -public struct FfiConverterTypeAccountInfoError: FfiConverterRustBuffer { - typealias SwiftType = AccountInfoError - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AccountInfoError { - let variant: Int32 = try readInt(&buf) - switch variant { - - - - case 1: return .InvalidExtendedKey( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 2: return .InvalidAddress( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 3: return .ElectrumError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 4: return .WalletError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 5: return .SyncError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 6: return .UnsupportedKeyType( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 7: return .NetworkMismatch( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 8: return .InvalidTxid( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 9: return .TransactionNotFound( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 10: return .WatcherError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - default: throw UniffiInternalError.unexpectedEnumCase +extension WakeKeyPair: Equatable, Hashable { + public static func ==(lhs: WakeKeyPair, rhs: WakeKeyPair) -> Bool { + if lhs.secretKeyHex != rhs.secretKeyHex { + return false + } + if lhs.publicKeyHex != rhs.publicKeyHex { + return false } + return true } - public static func write(_ value: AccountInfoError, into buf: inout [UInt8]) { - switch value { - - - - - - case let .InvalidExtendedKey(errorDetails): - writeInt(&buf, Int32(1)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .InvalidAddress(errorDetails): - writeInt(&buf, Int32(2)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .ElectrumError(errorDetails): - writeInt(&buf, Int32(3)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .WalletError(errorDetails): - writeInt(&buf, Int32(4)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .SyncError(errorDetails): - writeInt(&buf, Int32(5)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .UnsupportedKeyType(errorDetails): - writeInt(&buf, Int32(6)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .NetworkMismatch(errorDetails): - writeInt(&buf, Int32(7)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .InvalidTxid(errorDetails): - writeInt(&buf, Int32(8)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .TransactionNotFound(errorDetails): - writeInt(&buf, Int32(9)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .WatcherError(errorDetails): - writeInt(&buf, Int32(10)) - FfiConverterString.write(errorDetails, into: &buf) - - } + public func hash(into hasher: inout Hasher) { + hasher.combine(secretKeyHex) + hasher.combine(publicKeyHex) } } +extension WakeKeyPair: Codable {} + + #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeAccountInfoError_lift(_ buf: RustBuffer) throws -> AccountInfoError { - return try FfiConverterTypeAccountInfoError.lift(buf) +public struct FfiConverterTypeWakeKeyPair: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeKeyPair { + return + try WakeKeyPair( + secretKeyHex: FfiConverterString.read(from: &buf), + publicKeyHex: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: WakeKeyPair, into buf: inout [UInt8]) { + FfiConverterString.write(value.secretKeyHex, into: &buf) + FfiConverterString.write(value.publicKeyHex, into: &buf) + } } + #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeAccountInfoError_lower(_ value: AccountInfoError) -> RustBuffer { - return FfiConverterTypeAccountInfoError.lower(value) +public func FfiConverterTypeWakeKeyPair_lift(_ buf: RustBuffer) throws -> WakeKeyPair { + return try FfiConverterTypeWakeKeyPair.lift(buf) } - -extension AccountInfoError: Equatable, Hashable {} - -extension AccountInfoError: Codable {} - - - - -extension AccountInfoError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWakeKeyPair_lower(_ value: WakeKeyPair) -> RustBuffer { + return FfiConverterTypeWakeKeyPair.lower(value) } - - -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Account type classification for extended public keys. - * - * Determines the BIP standard, derivation path purpose, and script type. + * The gateway's view of a registered device. */ - -public enum AccountType { - - /** - * BIP44 legacy (P2PKH) — xpub/tpub prefix - */ - case legacy +public struct WakeRegistration { + public var deviceId: String + public var identities: [String] /** - * BIP49 wrapped segwit (P2SH-P2WPKH) — ypub/upub prefix + * Stored topics: the known names and every pattern. */ - case wrappedSegwit + public var topics: [String] /** - * BIP84 native segwit (P2WPKH) — zpub/vpub prefix + * Exact names the gateway does not know. They are not stored. */ - case nativeSegwit - /** - * BIP86 taproot (P2TR) - */ - case taproot -} + public var unknownTopics: [String] + public var serverTime: UInt64 + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(deviceId: String, identities: [String], + /** + * Stored topics: the known names and every pattern. + */topics: [String], + /** + * Exact names the gateway does not know. They are not stored. + */unknownTopics: [String], serverTime: UInt64) { + self.deviceId = deviceId + self.identities = identities + self.topics = topics + self.unknownTopics = unknownTopics + self.serverTime = serverTime + } +} #if compiler(>=6) -extension AccountType: Sendable {} +extension WakeRegistration: Sendable {} #endif -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeAccountType: FfiConverterRustBuffer { - typealias SwiftType = AccountType - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AccountType { - let variant: Int32 = try readInt(&buf) - switch variant { - - case 1: return .legacy - - case 2: return .wrappedSegwit - - case 3: return .nativeSegwit - - case 4: return .taproot - - default: throw UniffiInternalError.unexpectedEnumCase +extension WakeRegistration: Equatable, Hashable { + public static func ==(lhs: WakeRegistration, rhs: WakeRegistration) -> Bool { + if lhs.deviceId != rhs.deviceId { + return false } + if lhs.identities != rhs.identities { + return false + } + if lhs.topics != rhs.topics { + return false + } + if lhs.unknownTopics != rhs.unknownTopics { + return false + } + if lhs.serverTime != rhs.serverTime { + return false + } + return true } - public static func write(_ value: AccountType, into buf: inout [UInt8]) { - switch value { - - - case .legacy: - writeInt(&buf, Int32(1)) - - - case .wrappedSegwit: - writeInt(&buf, Int32(2)) - - - case .nativeSegwit: - writeInt(&buf, Int32(3)) - - - case .taproot: - writeInt(&buf, Int32(4)) - - } + public func hash(into hasher: inout Hasher) { + hasher.combine(deviceId) + hasher.combine(identities) + hasher.combine(topics) + hasher.combine(unknownTopics) + hasher.combine(serverTime) } } +extension WakeRegistration: Codable {} + + #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeAccountType_lift(_ buf: RustBuffer) throws -> AccountType { - return try FfiConverterTypeAccountType.lift(buf) +public struct FfiConverterTypeWakeRegistration: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeRegistration { + return + try WakeRegistration( + deviceId: FfiConverterString.read(from: &buf), + identities: FfiConverterSequenceString.read(from: &buf), + topics: FfiConverterSequenceString.read(from: &buf), + unknownTopics: FfiConverterSequenceString.read(from: &buf), + serverTime: FfiConverterUInt64.read(from: &buf) + ) + } + + public static func write(_ value: WakeRegistration, into buf: inout [UInt8]) { + FfiConverterString.write(value.deviceId, into: &buf) + FfiConverterSequenceString.write(value.identities, into: &buf) + FfiConverterSequenceString.write(value.topics, into: &buf) + FfiConverterSequenceString.write(value.unknownTopics, into: &buf) + FfiConverterUInt64.write(value.serverTime, into: &buf) + } } + #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeAccountType_lower(_ value: AccountType) -> RustBuffer { - return FfiConverterTypeAccountType.lower(value) +public func FfiConverterTypeWakeRegistration_lift(_ buf: RustBuffer) throws -> WakeRegistration { + return try FfiConverterTypeWakeRegistration.lift(buf) } - -extension AccountType: Equatable, Hashable {} - -extension AccountType: Codable {} +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWakeRegistration_lower(_ value: WakeRegistration) -> RustBuffer { + return FfiConverterTypeWakeRegistration.lower(value) +} +/** + * A registration ready to be signed. Every identity signs `message`: `ln:` + * through the Lightning node's message signing (ldk-node `signMessage`), + * `pk:` through `wake_sign_pubky_proof`. + */ +public struct WakeRegistrationRequest { + /** + * The gateway's audience, from `wake_server_info`. + */ + public var audience: String + public var app: String + public var installId: String + public var platform: WakePlatform + public var environment: WakeEnvironment + public var pushToken: String + /** + * Compressed secp256k1 public key, 66 lowercase hex characters. + */ + public var encryptionPublicKey: String + /** + * `WakeDeviceSecret.sha256_hex`. + */ + public var secretSha256: String + /** + * One `ln:` and/or one `pk:` identity. + */ + public var identities: [String] + /** + * Topic names and `.*` patterns. + */ + public var topics: [String] + /** + * Unix seconds. + */ + public var timestamp: UInt64 + /** + * The exact UTF-8 text every identity signs. + */ + public var message: String + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * The gateway's audience, from `wake_server_info`. + */audience: String, app: String, installId: String, platform: WakePlatform, environment: WakeEnvironment, pushToken: String, + /** + * Compressed secp256k1 public key, 66 lowercase hex characters. + */encryptionPublicKey: String, + /** + * `WakeDeviceSecret.sha256_hex`. + */secretSha256: String, + /** + * One `ln:` and/or one `pk:` identity. + */identities: [String], + /** + * Topic names and `.*` patterns. + */topics: [String], + /** + * Unix seconds. + */timestamp: UInt64, + /** + * The exact UTF-8 text every identity signs. + */message: String) { + self.audience = audience + self.app = app + self.installId = installId + self.platform = platform + self.environment = environment + self.pushToken = pushToken + self.encryptionPublicKey = encryptionPublicKey + self.secretSha256 = secretSha256 + self.identities = identities + self.topics = topics + self.timestamp = timestamp + self.message = message + } +} +#if compiler(>=6) +extension WakeRegistrationRequest: Sendable {} +#endif -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +extension WakeRegistrationRequest: Equatable, Hashable { + public static func ==(lhs: WakeRegistrationRequest, rhs: WakeRegistrationRequest) -> Bool { + if lhs.audience != rhs.audience { + return false + } + if lhs.app != rhs.app { + return false + } + if lhs.installId != rhs.installId { + return false + } + if lhs.platform != rhs.platform { + return false + } + if lhs.environment != rhs.environment { + return false + } + if lhs.pushToken != rhs.pushToken { + return false + } + if lhs.encryptionPublicKey != rhs.encryptionPublicKey { + return false + } + if lhs.secretSha256 != rhs.secretSha256 { + return false + } + if lhs.identities != rhs.identities { + return false + } + if lhs.topics != rhs.topics { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + if lhs.message != rhs.message { + return false + } + return true + } -public enum Activity { - - case onchain(OnchainActivity - ) - case lightning(LightningActivity - ) + public func hash(into hasher: inout Hasher) { + hasher.combine(audience) + hasher.combine(app) + hasher.combine(installId) + hasher.combine(platform) + hasher.combine(environment) + hasher.combine(pushToken) + hasher.combine(encryptionPublicKey) + hasher.combine(secretSha256) + hasher.combine(identities) + hasher.combine(topics) + hasher.combine(timestamp) + hasher.combine(message) + } } +extension WakeRegistrationRequest: Codable {} + -#if compiler(>=6) -extension Activity: Sendable {} -#endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeActivity: FfiConverterRustBuffer { - typealias SwiftType = Activity - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Activity { - let variant: Int32 = try readInt(&buf) - switch variant { - - case 1: return .onchain(try FfiConverterTypeOnchainActivity.read(from: &buf) - ) - - case 2: return .lightning(try FfiConverterTypeLightningActivity.read(from: &buf) +public struct FfiConverterTypeWakeRegistrationRequest: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeRegistrationRequest { + return + try WakeRegistrationRequest( + audience: FfiConverterString.read(from: &buf), + app: FfiConverterString.read(from: &buf), + installId: FfiConverterString.read(from: &buf), + platform: FfiConverterTypeWakePlatform.read(from: &buf), + environment: FfiConverterTypeWakeEnvironment.read(from: &buf), + pushToken: FfiConverterString.read(from: &buf), + encryptionPublicKey: FfiConverterString.read(from: &buf), + secretSha256: FfiConverterString.read(from: &buf), + identities: FfiConverterSequenceString.read(from: &buf), + topics: FfiConverterSequenceString.read(from: &buf), + timestamp: FfiConverterUInt64.read(from: &buf), + message: FfiConverterString.read(from: &buf) ) - - default: throw UniffiInternalError.unexpectedEnumCase - } } - public static func write(_ value: Activity, into buf: inout [UInt8]) { - switch value { - - - case let .onchain(v1): - writeInt(&buf, Int32(1)) - FfiConverterTypeOnchainActivity.write(v1, into: &buf) - - - case let .lightning(v1): - writeInt(&buf, Int32(2)) - FfiConverterTypeLightningActivity.write(v1, into: &buf) - - } + public static func write(_ value: WakeRegistrationRequest, into buf: inout [UInt8]) { + FfiConverterString.write(value.audience, into: &buf) + FfiConverterString.write(value.app, into: &buf) + FfiConverterString.write(value.installId, into: &buf) + FfiConverterTypeWakePlatform.write(value.platform, into: &buf) + FfiConverterTypeWakeEnvironment.write(value.environment, into: &buf) + FfiConverterString.write(value.pushToken, into: &buf) + FfiConverterString.write(value.encryptionPublicKey, into: &buf) + FfiConverterString.write(value.secretSha256, into: &buf) + FfiConverterSequenceString.write(value.identities, into: &buf) + FfiConverterSequenceString.write(value.topics, into: &buf) + FfiConverterUInt64.write(value.timestamp, into: &buf) + FfiConverterString.write(value.message, into: &buf) } } @@ -17156,129 +17108,1431 @@ public struct FfiConverterTypeActivity: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeActivity_lift(_ buf: RustBuffer) throws -> Activity { - return try FfiConverterTypeActivity.lift(buf) +public func FfiConverterTypeWakeRegistrationRequest_lift(_ buf: RustBuffer) throws -> WakeRegistrationRequest { + return try FfiConverterTypeWakeRegistrationRequest.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeActivity_lower(_ value: Activity) -> RustBuffer { - return FfiConverterTypeActivity.lower(value) +public func FfiConverterTypeWakeRegistrationRequest_lower(_ value: WakeRegistrationRequest) -> RustBuffer { + return FfiConverterTypeWakeRegistrationRequest.lower(value) } -extension Activity: Equatable, Hashable {} +/** + * `GET /v1/info`. + */ +public struct WakeServerInfo { + /** + * The value registrations must be prepared with. + */ + public var audience: String + public var serverTime: UInt64 + public var version: String -extension Activity: Codable {} + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * The value registrations must be prepared with. + */audience: String, serverTime: UInt64, version: String) { + self.audience = audience + self.serverTime = serverTime + self.version = version + } +} + +#if compiler(>=6) +extension WakeServerInfo: Sendable {} +#endif +extension WakeServerInfo: Equatable, Hashable { + public static func ==(lhs: WakeServerInfo, rhs: WakeServerInfo) -> Bool { + if lhs.audience != rhs.audience { + return false + } + if lhs.serverTime != rhs.serverTime { + return false + } + if lhs.version != rhs.version { + return false + } + return true + } + public func hash(into hasher: inout Hasher) { + hasher.combine(audience) + hasher.combine(serverTime) + hasher.combine(version) + } +} +extension WakeServerInfo: Codable {} -public enum ActivityError: Swift.Error { +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeWakeServerInfo: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeServerInfo { + return + try WakeServerInfo( + audience: FfiConverterString.read(from: &buf), + serverTime: FfiConverterUInt64.read(from: &buf), + version: FfiConverterString.read(from: &buf) + ) + } - - - case InvalidActivity(errorDetails: String - ) - case InitializationError(errorDetails: String - ) - case InsertError(errorDetails: String - ) - case RetrievalError(errorDetails: String - ) - case DataError(errorDetails: String - ) - case ConnectionError(errorDetails: String - ) - case SerializationError(errorDetails: String - ) + public static func write(_ value: WakeServerInfo, into buf: inout [UInt8]) { + FfiConverterString.write(value.audience, into: &buf) + FfiConverterUInt64.write(value.serverTime, into: &buf) + FfiConverterString.write(value.version, into: &buf) + } } #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeActivityError: FfiConverterRustBuffer { - typealias SwiftType = ActivityError +public func FfiConverterTypeWakeServerInfo_lift(_ buf: RustBuffer) throws -> WakeServerInfo { + return try FfiConverterTypeWakeServerInfo.lift(buf) +} - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ActivityError { - let variant: Int32 = try readInt(&buf) - switch variant { +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWakeServerInfo_lower(_ value: WakeServerInfo) -> RustBuffer { + return FfiConverterTypeWakeServerInfo.lower(value) +} + + +/** + * A topic devices can subscribe to. + */ +public struct WakeTopic { + public var name: String + public var description: String + /** + * `background`, `alert` or `time_sensitive`. + */ + public var urgency: String + public var deadlineSecs: UInt32 + /** + * Whether a visible fallback alert may be shown for this topic. + */ + public var alertable: Bool + /** + * Alert title, for topics with display text. + */ + public var title: String? + /** + * Alert body, for topics with display text. + */ + public var body: String? + /** + * Peers (not services) send this topic, through grants. + */ + public var peer: Bool + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(name: String, description: String, + /** + * `background`, `alert` or `time_sensitive`. + */urgency: String, deadlineSecs: UInt32, + /** + * Whether a visible fallback alert may be shown for this topic. + */alertable: Bool, + /** + * Alert title, for topics with display text. + */title: String?, + /** + * Alert body, for topics with display text. + */body: String?, + /** + * Peers (not services) send this topic, through grants. + */peer: Bool) { + self.name = name + self.description = description + self.urgency = urgency + self.deadlineSecs = deadlineSecs + self.alertable = alertable + self.title = title + self.body = body + self.peer = peer + } +} + +#if compiler(>=6) +extension WakeTopic: Sendable {} +#endif + + +extension WakeTopic: Equatable, Hashable { + public static func ==(lhs: WakeTopic, rhs: WakeTopic) -> Bool { + if lhs.name != rhs.name { + return false + } + if lhs.description != rhs.description { + return false + } + if lhs.urgency != rhs.urgency { + return false + } + if lhs.deadlineSecs != rhs.deadlineSecs { + return false + } + if lhs.alertable != rhs.alertable { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.body != rhs.body { + return false + } + if lhs.peer != rhs.peer { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + hasher.combine(description) + hasher.combine(urgency) + hasher.combine(deadlineSecs) + hasher.combine(alertable) + hasher.combine(title) + hasher.combine(body) + hasher.combine(peer) + } +} + +extension WakeTopic: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeWakeTopic: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeTopic { + return + try WakeTopic( + name: FfiConverterString.read(from: &buf), + description: FfiConverterString.read(from: &buf), + urgency: FfiConverterString.read(from: &buf), + deadlineSecs: FfiConverterUInt32.read(from: &buf), + alertable: FfiConverterBool.read(from: &buf), + title: FfiConverterOptionString.read(from: &buf), + body: FfiConverterOptionString.read(from: &buf), + peer: FfiConverterBool.read(from: &buf) + ) + } + + public static func write(_ value: WakeTopic, into buf: inout [UInt8]) { + FfiConverterString.write(value.name, into: &buf) + FfiConverterString.write(value.description, into: &buf) + FfiConverterString.write(value.urgency, into: &buf) + FfiConverterUInt32.write(value.deadlineSecs, into: &buf) + FfiConverterBool.write(value.alertable, into: &buf) + FfiConverterOptionString.write(value.title, into: &buf) + FfiConverterOptionString.write(value.body, into: &buf) + FfiConverterBool.write(value.peer, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWakeTopic_lift(_ buf: RustBuffer) throws -> WakeTopic { + return try FfiConverterTypeWakeTopic.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWakeTopic_lower(_ value: WakeTopic) -> RustBuffer { + return FfiConverterTypeWakeTopic.lower(value) +} + + +/** + * Balance breakdown from BDK. + */ +public struct WalletBalance { + /** + * Confirmed and spendable balance (sats) + */ + public var confirmed: UInt64 + /** + * Immature coinbase outputs (sats) + */ + public var immature: UInt64 + /** + * Unconfirmed UTXOs from trusted sources (own change) (sats) + */ + public var trustedPending: UInt64 + /** + * Unconfirmed UTXOs from external sources (sats) + */ + public var untrustedPending: UInt64 + /** + * Total spendable: confirmed + trusted_pending (sats) + */ + public var spendable: UInt64 + /** + * Grand total: all categories (sats) + */ + public var total: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * Confirmed and spendable balance (sats) + */confirmed: UInt64, + /** + * Immature coinbase outputs (sats) + */immature: UInt64, + /** + * Unconfirmed UTXOs from trusted sources (own change) (sats) + */trustedPending: UInt64, + /** + * Unconfirmed UTXOs from external sources (sats) + */untrustedPending: UInt64, + /** + * Total spendable: confirmed + trusted_pending (sats) + */spendable: UInt64, + /** + * Grand total: all categories (sats) + */total: UInt64) { + self.confirmed = confirmed + self.immature = immature + self.trustedPending = trustedPending + self.untrustedPending = untrustedPending + self.spendable = spendable + self.total = total + } +} + +#if compiler(>=6) +extension WalletBalance: Sendable {} +#endif + + +extension WalletBalance: Equatable, Hashable { + public static func ==(lhs: WalletBalance, rhs: WalletBalance) -> Bool { + if lhs.confirmed != rhs.confirmed { + return false + } + if lhs.immature != rhs.immature { + return false + } + if lhs.trustedPending != rhs.trustedPending { + return false + } + if lhs.untrustedPending != rhs.untrustedPending { + return false + } + if lhs.spendable != rhs.spendable { + return false + } + if lhs.total != rhs.total { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(confirmed) + hasher.combine(immature) + hasher.combine(trustedPending) + hasher.combine(untrustedPending) + hasher.combine(spendable) + hasher.combine(total) + } +} + +extension WalletBalance: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeWalletBalance: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WalletBalance { + return + try WalletBalance( + confirmed: FfiConverterUInt64.read(from: &buf), + immature: FfiConverterUInt64.read(from: &buf), + trustedPending: FfiConverterUInt64.read(from: &buf), + untrustedPending: FfiConverterUInt64.read(from: &buf), + spendable: FfiConverterUInt64.read(from: &buf), + total: FfiConverterUInt64.read(from: &buf) + ) + } + + public static func write(_ value: WalletBalance, into buf: inout [UInt8]) { + FfiConverterUInt64.write(value.confirmed, into: &buf) + FfiConverterUInt64.write(value.immature, into: &buf) + FfiConverterUInt64.write(value.trustedPending, into: &buf) + FfiConverterUInt64.write(value.untrustedPending, into: &buf) + FfiConverterUInt64.write(value.spendable, into: &buf) + FfiConverterUInt64.write(value.total, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWalletBalance_lift(_ buf: RustBuffer) throws -> WalletBalance { + return try FfiConverterTypeWalletBalance.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWalletBalance_lower(_ value: WalletBalance) -> RustBuffer { + return FfiConverterTypeWalletBalance.lower(value) +} + + +/** + * Common parameters for creating and syncing a watch-only BDK wallet. + */ +public struct WalletParams { + /** + * Extended public key (xpub/ypub/zpub/tpub/upub/vpub) + */ + public var extendedKey: String + /** + * Electrum server URL for wallet sync + */ + public var electrumUrl: String + /** + * Root fingerprint hex (e.g. "73c5da0a"). Required for hardware wallet signing. + */ + public var fingerprint: String? + /** + * Bitcoin network (auto-detected from key prefix if not specified) + */ + public var network: Network? + /** + * Override account type for ambiguous key prefixes (xpub/tpub) + */ + public var accountType: AccountType? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * Extended public key (xpub/ypub/zpub/tpub/upub/vpub) + */extendedKey: String, + /** + * Electrum server URL for wallet sync + */electrumUrl: String, + /** + * Root fingerprint hex (e.g. "73c5da0a"). Required for hardware wallet signing. + */fingerprint: String?, + /** + * Bitcoin network (auto-detected from key prefix if not specified) + */network: Network?, + /** + * Override account type for ambiguous key prefixes (xpub/tpub) + */accountType: AccountType?) { + self.extendedKey = extendedKey + self.electrumUrl = electrumUrl + self.fingerprint = fingerprint + self.network = network + self.accountType = accountType + } +} + +#if compiler(>=6) +extension WalletParams: Sendable {} +#endif + + +extension WalletParams: Equatable, Hashable { + public static func ==(lhs: WalletParams, rhs: WalletParams) -> Bool { + if lhs.extendedKey != rhs.extendedKey { + return false + } + if lhs.electrumUrl != rhs.electrumUrl { + return false + } + if lhs.fingerprint != rhs.fingerprint { + return false + } + if lhs.network != rhs.network { + return false + } + if lhs.accountType != rhs.accountType { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(extendedKey) + hasher.combine(electrumUrl) + hasher.combine(fingerprint) + hasher.combine(network) + hasher.combine(accountType) + } +} + +extension WalletParams: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeWalletParams: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WalletParams { + return + try WalletParams( + extendedKey: FfiConverterString.read(from: &buf), + electrumUrl: FfiConverterString.read(from: &buf), + fingerprint: FfiConverterOptionString.read(from: &buf), + network: FfiConverterOptionTypeNetwork.read(from: &buf), + accountType: FfiConverterOptionTypeAccountType.read(from: &buf) + ) + } + + public static func write(_ value: WalletParams, into buf: inout [UInt8]) { + FfiConverterString.write(value.extendedKey, into: &buf) + FfiConverterString.write(value.electrumUrl, into: &buf) + FfiConverterOptionString.write(value.fingerprint, into: &buf) + FfiConverterOptionTypeNetwork.write(value.network, into: &buf) + FfiConverterOptionTypeAccountType.write(value.accountType, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWalletParams_lift(_ buf: RustBuffer) throws -> WalletParams { + return try FfiConverterTypeWalletParams.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWalletParams_lower(_ value: WalletParams) -> RustBuffer { + return FfiConverterTypeWalletParams.lower(value) +} + + +/** + * Parameters for starting an xpub transaction watcher. + */ +public struct WatcherParams { + /** + * Caller-supplied identifier for this watcher. + */ + public var watcherId: String + /** + * Wallet id that scopes the activities this watcher emits. Apps may use + * one wallet id for several account watchers and merge their snapshots. + */ + public var walletId: String + /** + * Extended public key (xpub/ypub/zpub/tpub/upub/vpub). + */ + public var extendedKey: String + /** + * Electrum server URL (e.g. "ssl://electrum.example.com:50002"). + */ + public var electrumUrl: String + /** + * Bitcoin network override (auto-detected from key prefix if None). + */ + public var network: Network? + /** + * Account type override (auto-detected from key prefix if None). + */ + public var accountType: AccountType? + /** + * Number of unused addresses to monitor beyond the last used + * (defaults to `DEFAULT_GAP_LIMIT` when None). + */ + public var gapLimit: UInt32? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * Caller-supplied identifier for this watcher. + */watcherId: String, + /** + * Wallet id that scopes the activities this watcher emits. Apps may use + * one wallet id for several account watchers and merge their snapshots. + */walletId: String, + /** + * Extended public key (xpub/ypub/zpub/tpub/upub/vpub). + */extendedKey: String, + /** + * Electrum server URL (e.g. "ssl://electrum.example.com:50002"). + */electrumUrl: String, + /** + * Bitcoin network override (auto-detected from key prefix if None). + */network: Network?, + /** + * Account type override (auto-detected from key prefix if None). + */accountType: AccountType?, + /** + * Number of unused addresses to monitor beyond the last used + * (defaults to `DEFAULT_GAP_LIMIT` when None). + */gapLimit: UInt32?) { + self.watcherId = watcherId + self.walletId = walletId + self.extendedKey = extendedKey + self.electrumUrl = electrumUrl + self.network = network + self.accountType = accountType + self.gapLimit = gapLimit + } +} + +#if compiler(>=6) +extension WatcherParams: Sendable {} +#endif + + +extension WatcherParams: Equatable, Hashable { + public static func ==(lhs: WatcherParams, rhs: WatcherParams) -> Bool { + if lhs.watcherId != rhs.watcherId { + return false + } + if lhs.walletId != rhs.walletId { + return false + } + if lhs.extendedKey != rhs.extendedKey { + return false + } + if lhs.electrumUrl != rhs.electrumUrl { + return false + } + if lhs.network != rhs.network { + return false + } + if lhs.accountType != rhs.accountType { + return false + } + if lhs.gapLimit != rhs.gapLimit { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(watcherId) + hasher.combine(walletId) + hasher.combine(extendedKey) + hasher.combine(electrumUrl) + hasher.combine(network) + hasher.combine(accountType) + hasher.combine(gapLimit) + } +} + +extension WatcherParams: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeWatcherParams: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WatcherParams { + return + try WatcherParams( + watcherId: FfiConverterString.read(from: &buf), + walletId: FfiConverterString.read(from: &buf), + extendedKey: FfiConverterString.read(from: &buf), + electrumUrl: FfiConverterString.read(from: &buf), + network: FfiConverterOptionTypeNetwork.read(from: &buf), + accountType: FfiConverterOptionTypeAccountType.read(from: &buf), + gapLimit: FfiConverterOptionUInt32.read(from: &buf) + ) + } + + public static func write(_ value: WatcherParams, into buf: inout [UInt8]) { + FfiConverterString.write(value.watcherId, into: &buf) + FfiConverterString.write(value.walletId, into: &buf) + FfiConverterString.write(value.extendedKey, into: &buf) + FfiConverterString.write(value.electrumUrl, into: &buf) + FfiConverterOptionTypeNetwork.write(value.network, into: &buf) + FfiConverterOptionTypeAccountType.write(value.accountType, into: &buf) + FfiConverterOptionUInt32.write(value.gapLimit, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWatcherParams_lift(_ buf: RustBuffer) throws -> WatcherParams { + return try FfiConverterTypeWatcherParams.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeWatcherParams_lower(_ value: WatcherParams) -> RustBuffer { + return FfiConverterTypeWatcherParams.lower(value) +} + + +/** + * Errors specific to account info operations (BDK/Electrum-based). + */ +public enum AccountInfoError: Swift.Error { + + + + /** + * The provided extended public key is invalid or cannot be parsed + */ + case InvalidExtendedKey(errorDetails: String + ) + /** + * The provided address is invalid + */ + case InvalidAddress(errorDetails: String + ) + /** + * Electrum connection or query failed + */ + case ElectrumError(errorDetails: String + ) + /** + * BDK wallet creation or operation error + */ + case WalletError(errorDetails: String + ) + /** + * Wallet sync with Electrum failed + */ + case SyncError(errorDetails: String + ) + /** + * The key type/prefix is not recognized + */ + case UnsupportedKeyType(errorDetails: String + ) + /** + * Network mismatch between key prefix and specified network + */ + case NetworkMismatch(errorDetails: String + ) + /** + * Invalid transaction ID provided + */ + case InvalidTxid(errorDetails: String + ) + /** + * A valid transaction ID was not found in the wallet + */ + case TransactionNotFound(errorDetails: String + ) + /** + * Watcher lifecycle or subscription error + */ + case WatcherError(errorDetails: String + ) +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeAccountInfoError: FfiConverterRustBuffer { + typealias SwiftType = AccountInfoError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AccountInfoError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .InvalidExtendedKey( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .InvalidAddress( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 3: return .ElectrumError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 4: return .WalletError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 5: return .SyncError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 6: return .UnsupportedKeyType( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 7: return .NetworkMismatch( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 8: return .InvalidTxid( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 9: return .TransactionNotFound( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 10: return .WatcherError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: AccountInfoError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .InvalidExtendedKey(errorDetails): + writeInt(&buf, Int32(1)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InvalidAddress(errorDetails): + writeInt(&buf, Int32(2)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ElectrumError(errorDetails): + writeInt(&buf, Int32(3)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .WalletError(errorDetails): + writeInt(&buf, Int32(4)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .SyncError(errorDetails): + writeInt(&buf, Int32(5)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .UnsupportedKeyType(errorDetails): + writeInt(&buf, Int32(6)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .NetworkMismatch(errorDetails): + writeInt(&buf, Int32(7)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InvalidTxid(errorDetails): + writeInt(&buf, Int32(8)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .TransactionNotFound(errorDetails): + writeInt(&buf, Int32(9)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .WatcherError(errorDetails): + writeInt(&buf, Int32(10)) + FfiConverterString.write(errorDetails, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountInfoError_lift(_ buf: RustBuffer) throws -> AccountInfoError { + return try FfiConverterTypeAccountInfoError.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountInfoError_lower(_ value: AccountInfoError) -> RustBuffer { + return FfiConverterTypeAccountInfoError.lower(value) +} + + +extension AccountInfoError: Equatable, Hashable {} + +extension AccountInfoError: Codable {} + + + + +extension AccountInfoError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Account type classification for extended public keys. + * + * Determines the BIP standard, derivation path purpose, and script type. + */ + +public enum AccountType { + + /** + * BIP44 legacy (P2PKH) — xpub/tpub prefix + */ + case legacy + /** + * BIP49 wrapped segwit (P2SH-P2WPKH) — ypub/upub prefix + */ + case wrappedSegwit + /** + * BIP84 native segwit (P2WPKH) — zpub/vpub prefix + */ + case nativeSegwit + /** + * BIP86 taproot (P2TR) + */ + case taproot +} + + +#if compiler(>=6) +extension AccountType: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeAccountType: FfiConverterRustBuffer { + typealias SwiftType = AccountType + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AccountType { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .legacy + + case 2: return .wrappedSegwit + + case 3: return .nativeSegwit + + case 4: return .taproot + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: AccountType, into buf: inout [UInt8]) { + switch value { + + + case .legacy: + writeInt(&buf, Int32(1)) + + + case .wrappedSegwit: + writeInt(&buf, Int32(2)) + + + case .nativeSegwit: + writeInt(&buf, Int32(3)) + + + case .taproot: + writeInt(&buf, Int32(4)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountType_lift(_ buf: RustBuffer) throws -> AccountType { + return try FfiConverterTypeAccountType.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeAccountType_lower(_ value: AccountType) -> RustBuffer { + return FfiConverterTypeAccountType.lower(value) +} + + +extension AccountType: Equatable, Hashable {} + +extension AccountType: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum Activity { + + case onchain(OnchainActivity + ) + case lightning(LightningActivity + ) +} + + +#if compiler(>=6) +extension Activity: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeActivity: FfiConverterRustBuffer { + typealias SwiftType = Activity + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Activity { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .onchain(try FfiConverterTypeOnchainActivity.read(from: &buf) + ) + + case 2: return .lightning(try FfiConverterTypeLightningActivity.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: Activity, into buf: inout [UInt8]) { + switch value { + + + case let .onchain(v1): + writeInt(&buf, Int32(1)) + FfiConverterTypeOnchainActivity.write(v1, into: &buf) + + + case let .lightning(v1): + writeInt(&buf, Int32(2)) + FfiConverterTypeLightningActivity.write(v1, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivity_lift(_ buf: RustBuffer) throws -> Activity { + return try FfiConverterTypeActivity.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivity_lower(_ value: Activity) -> RustBuffer { + return FfiConverterTypeActivity.lower(value) +} + + +extension Activity: Equatable, Hashable {} + +extension Activity: Codable {} + + + + + + + +public enum ActivityError: Swift.Error { + + + + case InvalidActivity(errorDetails: String + ) + case InitializationError(errorDetails: String + ) + case InsertError(errorDetails: String + ) + case RetrievalError(errorDetails: String + ) + case DataError(errorDetails: String + ) + case ConnectionError(errorDetails: String + ) + case SerializationError(errorDetails: String + ) +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeActivityError: FfiConverterRustBuffer { + typealias SwiftType = ActivityError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ActivityError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .InvalidActivity( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .InitializationError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 3: return .InsertError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 4: return .RetrievalError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 5: return .DataError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 6: return .ConnectionError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 7: return .SerializationError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: ActivityError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .InvalidActivity(errorDetails): + writeInt(&buf, Int32(1)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InitializationError(errorDetails): + writeInt(&buf, Int32(2)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InsertError(errorDetails): + writeInt(&buf, Int32(3)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .RetrievalError(errorDetails): + writeInt(&buf, Int32(4)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .DataError(errorDetails): + writeInt(&buf, Int32(5)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ConnectionError(errorDetails): + writeInt(&buf, Int32(6)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .SerializationError(errorDetails): + writeInt(&buf, Int32(7)) + FfiConverterString.write(errorDetails, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivityError_lift(_ buf: RustBuffer) throws -> ActivityError { + return try FfiConverterTypeActivityError.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivityError_lower(_ value: ActivityError) -> RustBuffer { + return FfiConverterTypeActivityError.lower(value) +} + + +extension ActivityError: Equatable, Hashable {} + +extension ActivityError: Codable {} + + + + +extension ActivityError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum ActivityFilter { + + case all + case lightning + case onchain +} + + +#if compiler(>=6) +extension ActivityFilter: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeActivityFilter: FfiConverterRustBuffer { + typealias SwiftType = ActivityFilter + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ActivityFilter { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .all + + case 2: return .lightning + + case 3: return .onchain + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: ActivityFilter, into buf: inout [UInt8]) { + switch value { + + + case .all: + writeInt(&buf, Int32(1)) + + + case .lightning: + writeInt(&buf, Int32(2)) + + + case .onchain: + writeInt(&buf, Int32(3)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivityFilter_lift(_ buf: RustBuffer) throws -> ActivityFilter { + return try FfiConverterTypeActivityFilter.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivityFilter_lower(_ value: ActivityFilter) -> RustBuffer { + return FfiConverterTypeActivityFilter.lower(value) +} + + +extension ActivityFilter: Equatable, Hashable {} + +extension ActivityFilter: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum ActivityType { + + case onchain + case lightning +} + + +#if compiler(>=6) +extension ActivityType: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeActivityType: FfiConverterRustBuffer { + typealias SwiftType = ActivityType + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ActivityType { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .onchain + + case 2: return .lightning + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: ActivityType, into buf: inout [UInt8]) { + switch value { + + + case .onchain: + writeInt(&buf, Int32(1)) + + + case .lightning: + writeInt(&buf, Int32(2)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivityType_lift(_ buf: RustBuffer) throws -> ActivityType { + return try FfiConverterTypeActivityType.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivityType_lower(_ value: ActivityType) -> RustBuffer { + return FfiConverterTypeActivityType.lower(value) +} + + +extension ActivityType: Equatable, Hashable {} + +extension ActivityType: Codable {} + + + + + + + +public enum AddressError: Swift.Error { + + + + case InvalidAddress + case InvalidNetwork + case MnemonicGenerationFailed + case InvalidMnemonic + case InvalidEntropy + case AddressDerivationFailed +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeAddressError: FfiConverterRustBuffer { + typealias SwiftType = AddressError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AddressError { + let variant: Int32 = try readInt(&buf) + switch variant { - case 1: return .InvalidActivity( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 2: return .InitializationError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 3: return .InsertError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 4: return .RetrievalError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 5: return .DataError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 6: return .ConnectionError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 7: return .SerializationError( - errorDetails: try FfiConverterString.read(from: &buf) - ) + case 1: return .InvalidAddress + case 2: return .InvalidNetwork + case 3: return .MnemonicGenerationFailed + case 4: return .InvalidMnemonic + case 5: return .InvalidEntropy + case 6: return .AddressDerivationFailed default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: ActivityError, into buf: inout [UInt8]) { + public static func write(_ value: AddressError, into buf: inout [UInt8]) { switch value { - case let .InvalidActivity(errorDetails): + case .InvalidAddress: writeInt(&buf, Int32(1)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .InitializationError(errorDetails): + + case .InvalidNetwork: writeInt(&buf, Int32(2)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .InsertError(errorDetails): + + case .MnemonicGenerationFailed: writeInt(&buf, Int32(3)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .RetrievalError(errorDetails): + + case .InvalidMnemonic: writeInt(&buf, Int32(4)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .DataError(errorDetails): + + case .InvalidEntropy: writeInt(&buf, Int32(5)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .ConnectionError(errorDetails): + + case .AddressDerivationFailed: writeInt(&buf, Int32(6)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .SerializationError(errorDetails): - writeInt(&buf, Int32(7)) - FfiConverterString.write(errorDetails, into: &buf) - } } } @@ -17287,26 +18541,26 @@ public struct FfiConverterTypeActivityError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeActivityError_lift(_ buf: RustBuffer) throws -> ActivityError { - return try FfiConverterTypeActivityError.lift(buf) +public func FfiConverterTypeAddressError_lift(_ buf: RustBuffer) throws -> AddressError { + return try FfiConverterTypeAddressError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeActivityError_lower(_ value: ActivityError) -> RustBuffer { - return FfiConverterTypeActivityError.lower(value) +public func FfiConverterTypeAddressError_lower(_ value: AddressError) -> RustBuffer { + return FfiConverterTypeAddressError.lower(value) } -extension ActivityError: Equatable, Hashable {} +extension AddressError: Equatable, Hashable {} -extension ActivityError: Codable {} +extension AddressError: Codable {} -extension ActivityError: Foundation.LocalizedError { +extension AddressError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -17318,53 +18572,74 @@ extension ActivityError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum ActivityFilter { +public enum AddressType { - case all - case lightning - case onchain + case p2pkh + case p2sh + case p2wpkh + case p2wsh + case p2tr + case unknown } #if compiler(>=6) -extension ActivityFilter: Sendable {} +extension AddressType: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeActivityFilter: FfiConverterRustBuffer { - typealias SwiftType = ActivityFilter +public struct FfiConverterTypeAddressType: FfiConverterRustBuffer { + typealias SwiftType = AddressType - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ActivityFilter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AddressType { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .all + case 1: return .p2pkh - case 2: return .lightning + case 2: return .p2sh - case 3: return .onchain + case 3: return .p2wpkh + + case 4: return .p2wsh + + case 5: return .p2tr + + case 6: return .unknown default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: ActivityFilter, into buf: inout [UInt8]) { + public static func write(_ value: AddressType, into buf: inout [UInt8]) { switch value { - case .all: + case .p2pkh: writeInt(&buf, Int32(1)) - case .lightning: + case .p2sh: writeInt(&buf, Int32(2)) - case .onchain: + case .p2wpkh: writeInt(&buf, Int32(3)) + + case .p2wsh: + writeInt(&buf, Int32(4)) + + + case .p2tr: + writeInt(&buf, Int32(5)) + + + case .unknown: + writeInt(&buf, Int32(6)) + } } } @@ -17373,21 +18648,21 @@ public struct FfiConverterTypeActivityFilter: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeActivityFilter_lift(_ buf: RustBuffer) throws -> ActivityFilter { - return try FfiConverterTypeActivityFilter.lift(buf) +public func FfiConverterTypeAddressType_lift(_ buf: RustBuffer) throws -> AddressType { + return try FfiConverterTypeAddressType.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeActivityFilter_lower(_ value: ActivityFilter) -> RustBuffer { - return FfiConverterTypeActivityFilter.lower(value) +public func FfiConverterTypeAddressType_lower(_ value: AddressType) -> RustBuffer { + return FfiConverterTypeAddressType.lower(value) } -extension ActivityFilter: Equatable, Hashable {} +extension AddressType: Equatable, Hashable {} -extension ActivityFilter: Codable {} +extension AddressType: Codable {} @@ -17397,46 +18672,60 @@ extension ActivityFilter: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum ActivityType { +public enum BitcoinNetworkEnum { - case onchain - case lightning + case mainnet + case testnet + case signet + case regtest } #if compiler(>=6) -extension ActivityType: Sendable {} +extension BitcoinNetworkEnum: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeActivityType: FfiConverterRustBuffer { - typealias SwiftType = ActivityType +public struct FfiConverterTypeBitcoinNetworkEnum: FfiConverterRustBuffer { + typealias SwiftType = BitcoinNetworkEnum - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ActivityType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BitcoinNetworkEnum { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .onchain + case 1: return .mainnet - case 2: return .lightning + case 2: return .testnet + + case 3: return .signet + + case 4: return .regtest default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: ActivityType, into buf: inout [UInt8]) { + public static func write(_ value: BitcoinNetworkEnum, into buf: inout [UInt8]) { switch value { - case .onchain: + case .mainnet: writeInt(&buf, Int32(1)) - case .lightning: + case .testnet: writeInt(&buf, Int32(2)) + + case .signet: + writeInt(&buf, Int32(3)) + + + case .regtest: + writeInt(&buf, Int32(4)) + } } } @@ -17445,21 +18734,21 @@ public struct FfiConverterTypeActivityType: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeActivityType_lift(_ buf: RustBuffer) throws -> ActivityType { - return try FfiConverterTypeActivityType.lift(buf) +public func FfiConverterTypeBitcoinNetworkEnum_lift(_ buf: RustBuffer) throws -> BitcoinNetworkEnum { + return try FfiConverterTypeBitcoinNetworkEnum.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeActivityType_lower(_ value: ActivityType) -> RustBuffer { - return FfiConverterTypeActivityType.lower(value) +public func FfiConverterTypeBitcoinNetworkEnum_lower(_ value: BitcoinNetworkEnum) -> RustBuffer { + return FfiConverterTypeBitcoinNetworkEnum.lower(value) } -extension ActivityType: Equatable, Hashable {} +extension BitcoinNetworkEnum: Equatable, Hashable {} -extension ActivityType: Codable {} +extension BitcoinNetworkEnum: Codable {} @@ -17467,73 +18756,169 @@ extension ActivityType: Codable {} -public enum AddressError: Swift.Error { +public enum BlocktankError: Swift.Error { - case InvalidAddress - case InvalidNetwork - case MnemonicGenerationFailed - case InvalidMnemonic - case InvalidEntropy - case AddressDerivationFailed + case HttpClient(errorDetails: String + ) + case BlocktankClient(errorDetails: String + ) + case InvalidBlocktank(errorDetails: String + ) + case InitializationError(errorDetails: String + ) + case InsertError(errorDetails: String + ) + case RetrievalError(errorDetails: String + ) + case DataError(errorDetails: String + ) + case ConnectionError(errorDetails: String + ) + case SerializationError(errorDetails: String + ) + case ChannelOpen(errorType: BtChannelOrderErrorType, errorDetails: String + ) + case OrderState(errorDetails: String + ) + case InvalidParameter(errorDetails: String + ) + case DatabaseError(errorDetails: String + ) } #if swift(>=5.8) @_documentation(visibility: private) -#endif -public struct FfiConverterTypeAddressError: FfiConverterRustBuffer { - typealias SwiftType = AddressError +#endif +public struct FfiConverterTypeBlocktankError: FfiConverterRustBuffer { + typealias SwiftType = BlocktankError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AddressError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BlocktankError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .InvalidAddress - case 2: return .InvalidNetwork - case 3: return .MnemonicGenerationFailed - case 4: return .InvalidMnemonic - case 5: return .InvalidEntropy - case 6: return .AddressDerivationFailed + case 1: return .HttpClient( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .BlocktankClient( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 3: return .InvalidBlocktank( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 4: return .InitializationError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 5: return .InsertError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 6: return .RetrievalError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 7: return .DataError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 8: return .ConnectionError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 9: return .SerializationError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 10: return .ChannelOpen( + errorType: try FfiConverterTypeBtChannelOrderErrorType.read(from: &buf), + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 11: return .OrderState( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 12: return .InvalidParameter( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 13: return .DatabaseError( + errorDetails: try FfiConverterString.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: AddressError, into buf: inout [UInt8]) { + public static func write(_ value: BlocktankError, into buf: inout [UInt8]) { switch value { - case .InvalidAddress: + case let .HttpClient(errorDetails): writeInt(&buf, Int32(1)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .InvalidNetwork: + case let .BlocktankClient(errorDetails): writeInt(&buf, Int32(2)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .MnemonicGenerationFailed: + case let .InvalidBlocktank(errorDetails): writeInt(&buf, Int32(3)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .InvalidMnemonic: + case let .InitializationError(errorDetails): writeInt(&buf, Int32(4)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .InvalidEntropy: + case let .InsertError(errorDetails): writeInt(&buf, Int32(5)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .AddressDerivationFailed: + case let .RetrievalError(errorDetails): writeInt(&buf, Int32(6)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .DataError(errorDetails): + writeInt(&buf, Int32(7)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ConnectionError(errorDetails): + writeInt(&buf, Int32(8)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .SerializationError(errorDetails): + writeInt(&buf, Int32(9)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ChannelOpen(errorType,errorDetails): + writeInt(&buf, Int32(10)) + FfiConverterTypeBtChannelOrderErrorType.write(errorType, into: &buf) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .OrderState(errorDetails): + writeInt(&buf, Int32(11)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InvalidParameter(errorDetails): + writeInt(&buf, Int32(12)) + FfiConverterString.write(errorDetails, into: &buf) + + case let .DatabaseError(errorDetails): + writeInt(&buf, Int32(13)) + FfiConverterString.write(errorDetails, into: &buf) + } } } @@ -17542,26 +18927,26 @@ public struct FfiConverterTypeAddressError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeAddressError_lift(_ buf: RustBuffer) throws -> AddressError { - return try FfiConverterTypeAddressError.lift(buf) +public func FfiConverterTypeBlocktankError_lift(_ buf: RustBuffer) throws -> BlocktankError { + return try FfiConverterTypeBlocktankError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeAddressError_lower(_ value: AddressError) -> RustBuffer { - return FfiConverterTypeAddressError.lower(value) +public func FfiConverterTypeBlocktankError_lower(_ value: BlocktankError) -> RustBuffer { + return FfiConverterTypeBlocktankError.lower(value) } -extension AddressError: Equatable, Hashable {} +extension BlocktankError: Equatable, Hashable {} -extension AddressError: Codable {} +extension BlocktankError: Codable {} -extension AddressError: Foundation.LocalizedError { +extension BlocktankError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -17570,77 +18955,131 @@ extension AddressError: Foundation.LocalizedError { -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum AddressType { +/** + * Errors surfaced by the Boltz swaps module. + */ +public enum BoltzError: Swift.Error { + - case p2pkh - case p2sh - case p2wpkh - case p2wsh - case p2tr - case unknown + + case InitializationError(errorDetails: String + ) + case ConnectionError(errorDetails: String + ) + case DatabaseError(errorDetails: String + ) + case ApiError(errorDetails: String + ) + case SwapError(errorDetails: String + ) + case BroadcastError(errorDetails: String + ) + case InvalidInput(errorDetails: String + ) + case SerializationError(errorDetails: String + ) + case NotFound(errorDetails: String + ) } -#if compiler(>=6) -extension AddressType: Sendable {} -#endif - #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeAddressType: FfiConverterRustBuffer { - typealias SwiftType = AddressType +public struct FfiConverterTypeBoltzError: FfiConverterRustBuffer { + typealias SwiftType = BoltzError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AddressType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzError { let variant: Int32 = try readInt(&buf) switch variant { + - case 1: return .p2pkh - - case 2: return .p2sh - - case 3: return .p2wpkh - - case 4: return .p2wsh - - case 5: return .p2tr - - case 6: return .unknown + - default: throw UniffiInternalError.unexpectedEnumCase + case 1: return .InitializationError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .ConnectionError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 3: return .DatabaseError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 4: return .ApiError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 5: return .SwapError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 6: return .BroadcastError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 7: return .InvalidInput( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 8: return .SerializationError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 9: return .NotFound( + errorDetails: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: AddressType, into buf: inout [UInt8]) { + public static func write(_ value: BoltzError, into buf: inout [UInt8]) { switch value { + + - case .p2pkh: - writeInt(&buf, Int32(1)) + case let .InitializationError(errorDetails): + writeInt(&buf, Int32(1)) + FfiConverterString.write(errorDetails, into: &buf) + - case .p2sh: + case let .ConnectionError(errorDetails): writeInt(&buf, Int32(2)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .p2wpkh: + case let .DatabaseError(errorDetails): writeInt(&buf, Int32(3)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .p2wsh: + case let .ApiError(errorDetails): writeInt(&buf, Int32(4)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .p2tr: + case let .SwapError(errorDetails): writeInt(&buf, Int32(5)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .unknown: + case let .BroadcastError(errorDetails): writeInt(&buf, Int32(6)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .InvalidInput(errorDetails): + writeInt(&buf, Int32(7)) + FfiConverterString.write(errorDetails, into: &buf) + + case let .SerializationError(errorDetails): + writeInt(&buf, Int32(8)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .NotFound(errorDetails): + writeInt(&buf, Int32(9)) + FfiConverterString.write(errorDetails, into: &buf) + } } } @@ -17649,50 +19088,60 @@ public struct FfiConverterTypeAddressType: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeAddressType_lift(_ buf: RustBuffer) throws -> AddressType { - return try FfiConverterTypeAddressType.lift(buf) +public func FfiConverterTypeBoltzError_lift(_ buf: RustBuffer) throws -> BoltzError { + return try FfiConverterTypeBoltzError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeAddressType_lower(_ value: AddressType) -> RustBuffer { - return FfiConverterTypeAddressType.lower(value) +public func FfiConverterTypeBoltzError_lower(_ value: BoltzError) -> RustBuffer { + return FfiConverterTypeBoltzError.lower(value) } -extension AddressType: Equatable, Hashable {} +extension BoltzError: Equatable, Hashable {} + +extension BoltzError: Codable {} -extension AddressType: Codable {} +extension BoltzError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Bitcoin network selection for Boltz swaps. Maps to the networks Boltz + * operates on (mainnet, testnet, regtest). + */ -public enum BitcoinNetworkEnum { +public enum BoltzNetwork { case mainnet case testnet - case signet case regtest } #if compiler(>=6) -extension BitcoinNetworkEnum: Sendable {} +extension BoltzNetwork: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBitcoinNetworkEnum: FfiConverterRustBuffer { - typealias SwiftType = BitcoinNetworkEnum +public struct FfiConverterTypeBoltzNetwork: FfiConverterRustBuffer { + typealias SwiftType = BoltzNetwork - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BitcoinNetworkEnum { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzNetwork { let variant: Int32 = try readInt(&buf) switch variant { @@ -17700,15 +19149,13 @@ public struct FfiConverterTypeBitcoinNetworkEnum: FfiConverterRustBuffer { case 2: return .testnet - case 3: return .signet - - case 4: return .regtest + case 3: return .regtest default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BitcoinNetworkEnum, into buf: inout [UInt8]) { + public static func write(_ value: BoltzNetwork, into buf: inout [UInt8]) { switch value { @@ -17720,12 +19167,8 @@ public struct FfiConverterTypeBitcoinNetworkEnum: FfiConverterRustBuffer { writeInt(&buf, Int32(2)) - case .signet: - writeInt(&buf, Int32(3)) - - case .regtest: - writeInt(&buf, Int32(4)) + writeInt(&buf, Int32(3)) } } @@ -17735,190 +19178,115 @@ public struct FfiConverterTypeBitcoinNetworkEnum: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBitcoinNetworkEnum_lift(_ buf: RustBuffer) throws -> BitcoinNetworkEnum { - return try FfiConverterTypeBitcoinNetworkEnum.lift(buf) +public func FfiConverterTypeBoltzNetwork_lift(_ buf: RustBuffer) throws -> BoltzNetwork { + return try FfiConverterTypeBoltzNetwork.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBitcoinNetworkEnum_lower(_ value: BitcoinNetworkEnum) -> RustBuffer { - return FfiConverterTypeBitcoinNetworkEnum.lower(value) +public func FfiConverterTypeBoltzNetwork_lower(_ value: BoltzNetwork) -> RustBuffer { + return FfiConverterTypeBoltzNetwork.lower(value) } -extension BitcoinNetworkEnum: Equatable, Hashable {} - -extension BitcoinNetworkEnum: Codable {} +extension BoltzNetwork: Equatable, Hashable {} +extension BoltzNetwork: Codable {} -public enum BlocktankError: Swift.Error { +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Events emitted to a registered [`crate::modules::boltz::BoltzEventListener`] + * as swaps progress through their lifecycle. + */ +public enum BoltzSwapEvent { - - case HttpClient(errorDetails: String - ) - case BlocktankClient(errorDetails: String - ) - case InvalidBlocktank(errorDetails: String - ) - case InitializationError(errorDetails: String - ) - case InsertError(errorDetails: String - ) - case RetrievalError(errorDetails: String - ) - case DataError(errorDetails: String - ) - case ConnectionError(errorDetails: String - ) - case SerializationError(errorDetails: String - ) - case ChannelOpen(errorType: BtChannelOrderErrorType, errorDetails: String + /** + * The swap transitioned to a new status. + */ + case statusUpdate(swapId: String, status: BoltzSwapStatus ) - case OrderState(errorDetails: String + /** + * A reverse swap was claimed onchain. `txid` is the claim transaction. + */ + case claimed(swapId: String, txid: String ) - case InvalidParameter(errorDetails: String + /** + * A submarine swap was refunded onchain. `txid` is the refund transaction. + */ + case refunded(swapId: String, txid: String ) - case DatabaseError(errorDetails: String + /** + * An error occurred while processing the swap (e.g. an auto-claim failed). + */ + case error(swapId: String, message: String ) } +#if compiler(>=6) +extension BoltzSwapEvent: Sendable {} +#endif + #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBlocktankError: FfiConverterRustBuffer { - typealias SwiftType = BlocktankError +public struct FfiConverterTypeBoltzSwapEvent: FfiConverterRustBuffer { + typealias SwiftType = BoltzSwapEvent - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BlocktankError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwapEvent { let variant: Int32 = try readInt(&buf) switch variant { - - + case 1: return .statusUpdate(swapId: try FfiConverterString.read(from: &buf), status: try FfiConverterTypeBoltzSwapStatus.read(from: &buf) + ) - case 1: return .HttpClient( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 2: return .BlocktankClient( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 3: return .InvalidBlocktank( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 4: return .InitializationError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 5: return .InsertError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 6: return .RetrievalError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 7: return .DataError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 8: return .ConnectionError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 9: return .SerializationError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 10: return .ChannelOpen( - errorType: try FfiConverterTypeBtChannelOrderErrorType.read(from: &buf), - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 11: return .OrderState( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 12: return .InvalidParameter( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 13: return .DatabaseError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - - default: throw UniffiInternalError.unexpectedEnumCase + case 2: return .claimed(swapId: try FfiConverterString.read(from: &buf), txid: try FfiConverterString.read(from: &buf) + ) + + case 3: return .refunded(swapId: try FfiConverterString.read(from: &buf), txid: try FfiConverterString.read(from: &buf) + ) + + case 4: return .error(swapId: try FfiConverterString.read(from: &buf), message: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BlocktankError, into buf: inout [UInt8]) { + public static func write(_ value: BoltzSwapEvent, into buf: inout [UInt8]) { switch value { - - - - case let .HttpClient(errorDetails): + case let .statusUpdate(swapId,status): writeInt(&buf, Int32(1)) - FfiConverterString.write(errorDetails, into: &buf) + FfiConverterString.write(swapId, into: &buf) + FfiConverterTypeBoltzSwapStatus.write(status, into: &buf) - case let .BlocktankClient(errorDetails): + case let .claimed(swapId,txid): writeInt(&buf, Int32(2)) - FfiConverterString.write(errorDetails, into: &buf) + FfiConverterString.write(swapId, into: &buf) + FfiConverterString.write(txid, into: &buf) - case let .InvalidBlocktank(errorDetails): + case let .refunded(swapId,txid): writeInt(&buf, Int32(3)) - FfiConverterString.write(errorDetails, into: &buf) + FfiConverterString.write(swapId, into: &buf) + FfiConverterString.write(txid, into: &buf) - case let .InitializationError(errorDetails): + case let .error(swapId,message): writeInt(&buf, Int32(4)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .InsertError(errorDetails): - writeInt(&buf, Int32(5)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .RetrievalError(errorDetails): - writeInt(&buf, Int32(6)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .DataError(errorDetails): - writeInt(&buf, Int32(7)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .ConnectionError(errorDetails): - writeInt(&buf, Int32(8)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .SerializationError(errorDetails): - writeInt(&buf, Int32(9)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .ChannelOpen(errorType,errorDetails): - writeInt(&buf, Int32(10)) - FfiConverterTypeBtChannelOrderErrorType.write(errorType, into: &buf) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .OrderState(errorDetails): - writeInt(&buf, Int32(11)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .InvalidParameter(errorDetails): - writeInt(&buf, Int32(12)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .DatabaseError(errorDetails): - writeInt(&buf, Int32(13)) - FfiConverterString.write(errorDetails, into: &buf) + FfiConverterString.write(swapId, into: &buf) + FfiConverterString.write(message, into: &buf) } } @@ -17928,158 +19296,224 @@ public struct FfiConverterTypeBlocktankError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBlocktankError_lift(_ buf: RustBuffer) throws -> BlocktankError { - return try FfiConverterTypeBlocktankError.lift(buf) +public func FfiConverterTypeBoltzSwapEvent_lift(_ buf: RustBuffer) throws -> BoltzSwapEvent { + return try FfiConverterTypeBoltzSwapEvent.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBlocktankError_lower(_ value: BlocktankError) -> RustBuffer { - return FfiConverterTypeBlocktankError.lower(value) +public func FfiConverterTypeBoltzSwapEvent_lower(_ value: BoltzSwapEvent) -> RustBuffer { + return FfiConverterTypeBoltzSwapEvent.lower(value) } -extension BlocktankError: Equatable, Hashable {} - -extension BlocktankError: Codable {} - - +extension BoltzSwapEvent: Equatable, Hashable {} +extension BoltzSwapEvent: Codable {} -extension BlocktankError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Errors surfaced by the Boltz swaps module. + * Typed view of the Boltz swap lifecycle. `Unknown` carries the raw status so + * new server-side states don't break the bindings. + * + * See . */ -public enum BoltzError: Swift.Error { +public enum BoltzSwapStatus { - - case InitializationError(errorDetails: String - ) - case ConnectionError(errorDetails: String - ) - case DatabaseError(errorDetails: String - ) - case ApiError(errorDetails: String - ) - case SwapError(errorDetails: String - ) - case BroadcastError(errorDetails: String - ) - case InvalidInput(errorDetails: String - ) - case SerializationError(errorDetails: String - ) - case NotFound(errorDetails: String + /** + * `swap.created` — initial state. + */ + case swapCreated + /** + * `invoice.set` — invoice attached to a submarine swap. + */ + case invoiceSet + /** + * `transaction.mempool` — a lockup transaction is in the mempool. + */ + case transactionMempool + /** + * `transaction.confirmed` — a lockup transaction confirmed. + */ + case transactionConfirmed + /** + * `invoice.pending` — Boltz is paying the submarine swap invoice. + */ + case invoicePending + /** + * `invoice.paid` — submarine swap invoice paid by Boltz. + */ + case invoicePaid + /** + * `invoice.settled` — reverse swap invoice settled (preimage revealed). + */ + case invoiceSettled + /** + * `invoice.failedToPay` — submarine swap invoice could not be paid; refund. + */ + case invoiceFailedToPay + /** + * `invoice.expired` — reverse swap invoice expired before payment. + */ + case invoiceExpired + /** + * `transaction.claim.pending` — Boltz ready for a cooperative claim. + */ + case transactionClaimPending + /** + * `transaction.claimed` — onchain funds claimed. + */ + case transactionClaimed + /** + * `transaction.refunded` — onchain funds refunded. + */ + case transactionRefunded + /** + * `transaction.lockupFailed` — wrong amount locked; can refund. + */ + case transactionLockupFailed + /** + * `transaction.failed` — Boltz failed to lock the agreed funds. + */ + case transactionFailed + /** + * `swap.expired` — swap expired without completing. + */ + case swapExpired + /** + * Any status not yet modelled. `raw` holds the verbatim Boltz status. + */ + case unknown(raw: String ) } +#if compiler(>=6) +extension BoltzSwapStatus: Sendable {} +#endif + #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBoltzError: FfiConverterRustBuffer { - typealias SwiftType = BoltzError +public struct FfiConverterTypeBoltzSwapStatus: FfiConverterRustBuffer { + typealias SwiftType = BoltzSwapStatus - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwapStatus { let variant: Int32 = try readInt(&buf) switch variant { - - + case 1: return .swapCreated - case 1: return .InitializationError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 2: return .ConnectionError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 3: return .DatabaseError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 4: return .ApiError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 5: return .SwapError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 6: return .BroadcastError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 7: return .InvalidInput( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 8: return .SerializationError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 9: return .NotFound( - errorDetails: try FfiConverterString.read(from: &buf) - ) - - default: throw UniffiInternalError.unexpectedEnumCase + case 2: return .invoiceSet + + case 3: return .transactionMempool + + case 4: return .transactionConfirmed + + case 5: return .invoicePending + + case 6: return .invoicePaid + + case 7: return .invoiceSettled + + case 8: return .invoiceFailedToPay + + case 9: return .invoiceExpired + + case 10: return .transactionClaimPending + + case 11: return .transactionClaimed + + case 12: return .transactionRefunded + + case 13: return .transactionLockupFailed + + case 14: return .transactionFailed + + case 15: return .swapExpired + + case 16: return .unknown(raw: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BoltzError, into buf: inout [UInt8]) { + public static func write(_ value: BoltzSwapStatus, into buf: inout [UInt8]) { switch value { - - - - case let .InitializationError(errorDetails): + case .swapCreated: writeInt(&buf, Int32(1)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .ConnectionError(errorDetails): + + case .invoiceSet: writeInt(&buf, Int32(2)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .DatabaseError(errorDetails): + + case .transactionMempool: writeInt(&buf, Int32(3)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .ApiError(errorDetails): + + case .transactionConfirmed: writeInt(&buf, Int32(4)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .SwapError(errorDetails): + + case .invoicePending: writeInt(&buf, Int32(5)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .BroadcastError(errorDetails): + + case .invoicePaid: writeInt(&buf, Int32(6)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .InvalidInput(errorDetails): + + case .invoiceSettled: writeInt(&buf, Int32(7)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .SerializationError(errorDetails): + + case .invoiceFailedToPay: writeInt(&buf, Int32(8)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .NotFound(errorDetails): + + case .invoiceExpired: writeInt(&buf, Int32(9)) - FfiConverterString.write(errorDetails, into: &buf) + + + case .transactionClaimPending: + writeInt(&buf, Int32(10)) + + + case .transactionClaimed: + writeInt(&buf, Int32(11)) + + + case .transactionRefunded: + writeInt(&buf, Int32(12)) + + + case .transactionLockupFailed: + writeInt(&buf, Int32(13)) + + + case .transactionFailed: + writeInt(&buf, Int32(14)) + + + case .swapExpired: + writeInt(&buf, Int32(15)) + + + case let .unknown(raw): + writeInt(&buf, Int32(16)) + FfiConverterString.write(raw, into: &buf) } } @@ -18089,30 +19523,23 @@ public struct FfiConverterTypeBoltzError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzError_lift(_ buf: RustBuffer) throws -> BoltzError { - return try FfiConverterTypeBoltzError.lift(buf) +public func FfiConverterTypeBoltzSwapStatus_lift(_ buf: RustBuffer) throws -> BoltzSwapStatus { + return try FfiConverterTypeBoltzSwapStatus.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzError_lower(_ value: BoltzError) -> RustBuffer { - return FfiConverterTypeBoltzError.lower(value) +public func FfiConverterTypeBoltzSwapStatus_lower(_ value: BoltzSwapStatus) -> RustBuffer { + return FfiConverterTypeBoltzSwapStatus.lower(value) } -extension BoltzError: Equatable, Hashable {} - -extension BoltzError: Codable {} - +extension BoltzSwapStatus: Equatable, Hashable {} +extension BoltzSwapStatus: Codable {} -extension BoltzError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} @@ -18120,57 +19547,54 @@ extension BoltzError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Bitcoin network selection for Boltz swaps. Maps to the networks Boltz - * operates on (mainnet, testnet, regtest). + * The direction of a Boltz swap. + * + * - `Submarine`: onchain Bitcoin -> Lightning (the user locks onchain funds, + * Boltz pays a Lightning invoice). + * - `Reverse`: Lightning -> onchain Bitcoin (the user pays a Boltz hold + * invoice, Boltz locks onchain funds the user then claims). */ -public enum BoltzNetwork { +public enum BoltzSwapType { - case mainnet - case testnet - case regtest + case submarine + case reverse } #if compiler(>=6) -extension BoltzNetwork: Sendable {} +extension BoltzSwapType: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBoltzNetwork: FfiConverterRustBuffer { - typealias SwiftType = BoltzNetwork +public struct FfiConverterTypeBoltzSwapType: FfiConverterRustBuffer { + typealias SwiftType = BoltzSwapType - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzNetwork { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwapType { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .mainnet - - case 2: return .testnet + case 1: return .submarine - case 3: return .regtest + case 2: return .reverse default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BoltzNetwork, into buf: inout [UInt8]) { + public static func write(_ value: BoltzSwapType, into buf: inout [UInt8]) { switch value { - case .mainnet: + case .submarine: writeInt(&buf, Int32(1)) - case .testnet: + case .reverse: writeInt(&buf, Int32(2)) - - case .regtest: - writeInt(&buf, Int32(3)) - } } } @@ -18179,115 +19603,98 @@ public struct FfiConverterTypeBoltzNetwork: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzNetwork_lift(_ buf: RustBuffer) throws -> BoltzNetwork { - return try FfiConverterTypeBoltzNetwork.lift(buf) +public func FfiConverterTypeBoltzSwapType_lift(_ buf: RustBuffer) throws -> BoltzSwapType { + return try FfiConverterTypeBoltzSwapType.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzNetwork_lower(_ value: BoltzNetwork) -> RustBuffer { - return FfiConverterTypeBoltzNetwork.lower(value) +public func FfiConverterTypeBoltzSwapType_lower(_ value: BoltzSwapType) -> RustBuffer { + return FfiConverterTypeBoltzSwapType.lower(value) } -extension BoltzNetwork: Equatable, Hashable {} +extension BoltzSwapType: Equatable, Hashable {} -extension BoltzNetwork: Codable {} +extension BoltzSwapType: Codable {} -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -/** - * Events emitted to a registered [`crate::modules::boltz::BoltzEventListener`] - * as swaps progress through their lifecycle. - */ -public enum BoltzSwapEvent { +public enum BroadcastError: Swift.Error { + - /** - * The swap transitioned to a new status. - */ - case statusUpdate(swapId: String, status: BoltzSwapStatus + + case InvalidHex(errorDetails: String ) - /** - * A reverse swap was claimed onchain. `txid` is the claim transaction. - */ - case claimed(swapId: String, txid: String + case InvalidTransaction(errorDetails: String ) - /** - * A submarine swap was refunded onchain. `txid` is the refund transaction. - */ - case refunded(swapId: String, txid: String + case ElectrumError(errorDetails: String ) - /** - * An error occurred while processing the swap (e.g. an auto-claim failed). - */ - case error(swapId: String, message: String + case TaskError(errorDetails: String ) } -#if compiler(>=6) -extension BoltzSwapEvent: Sendable {} -#endif - #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBoltzSwapEvent: FfiConverterRustBuffer { - typealias SwiftType = BoltzSwapEvent +public struct FfiConverterTypeBroadcastError: FfiConverterRustBuffer { + typealias SwiftType = BroadcastError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwapEvent { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BroadcastError { let variant: Int32 = try readInt(&buf) switch variant { + - case 1: return .statusUpdate(swapId: try FfiConverterString.read(from: &buf), status: try FfiConverterTypeBoltzSwapStatus.read(from: &buf) - ) - - case 2: return .claimed(swapId: try FfiConverterString.read(from: &buf), txid: try FfiConverterString.read(from: &buf) - ) - - case 3: return .refunded(swapId: try FfiConverterString.read(from: &buf), txid: try FfiConverterString.read(from: &buf) - ) - - case 4: return .error(swapId: try FfiConverterString.read(from: &buf), message: try FfiConverterString.read(from: &buf) - ) + - default: throw UniffiInternalError.unexpectedEnumCase + case 1: return .InvalidHex( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .InvalidTransaction( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 3: return .ElectrumError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 4: return .TaskError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BoltzSwapEvent, into buf: inout [UInt8]) { + public static func write(_ value: BroadcastError, into buf: inout [UInt8]) { switch value { + + + - case let .statusUpdate(swapId,status): + case let .InvalidHex(errorDetails): writeInt(&buf, Int32(1)) - FfiConverterString.write(swapId, into: &buf) - FfiConverterTypeBoltzSwapStatus.write(status, into: &buf) + FfiConverterString.write(errorDetails, into: &buf) - case let .claimed(swapId,txid): + case let .InvalidTransaction(errorDetails): writeInt(&buf, Int32(2)) - FfiConverterString.write(swapId, into: &buf) - FfiConverterString.write(txid, into: &buf) + FfiConverterString.write(errorDetails, into: &buf) - case let .refunded(swapId,txid): + case let .ElectrumError(errorDetails): writeInt(&buf, Int32(3)) - FfiConverterString.write(swapId, into: &buf) - FfiConverterString.write(txid, into: &buf) + FfiConverterString.write(errorDetails, into: &buf) - case let .error(swapId,message): + case let .TaskError(errorDetails): writeInt(&buf, Int32(4)) - FfiConverterString.write(swapId, into: &buf) - FfiConverterString.write(message, into: &buf) + FfiConverterString.write(errorDetails, into: &buf) } } @@ -18297,225 +19704,184 @@ public struct FfiConverterTypeBoltzSwapEvent: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzSwapEvent_lift(_ buf: RustBuffer) throws -> BoltzSwapEvent { - return try FfiConverterTypeBoltzSwapEvent.lift(buf) +public func FfiConverterTypeBroadcastError_lift(_ buf: RustBuffer) throws -> BroadcastError { + return try FfiConverterTypeBroadcastError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzSwapEvent_lower(_ value: BoltzSwapEvent) -> RustBuffer { - return FfiConverterTypeBoltzSwapEvent.lower(value) +public func FfiConverterTypeBroadcastError_lower(_ value: BroadcastError) -> RustBuffer { + return FfiConverterTypeBroadcastError.lower(value) } -extension BoltzSwapEvent: Equatable, Hashable {} - -extension BoltzSwapEvent: Codable {} - - - +extension BroadcastError: Equatable, Hashable {} +extension BroadcastError: Codable {} -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -/** - * Typed view of the Boltz swap lifecycle. `Unknown` carries the raw status so - * new server-side states don't break the bindings. - * - * See . - */ -public enum BoltzSwapStatus { - - /** - * `swap.created` — initial state. - */ - case swapCreated - /** - * `invoice.set` — invoice attached to a submarine swap. - */ - case invoiceSet - /** - * `transaction.mempool` — a lockup transaction is in the mempool. - */ - case transactionMempool - /** - * `transaction.confirmed` — a lockup transaction confirmed. - */ - case transactionConfirmed - /** - * `invoice.pending` — Boltz is paying the submarine swap invoice. - */ - case invoicePending - /** - * `invoice.paid` — submarine swap invoice paid by Boltz. - */ - case invoicePaid - /** - * `invoice.settled` — reverse swap invoice settled (preimage revealed). - */ - case invoiceSettled - /** - * `invoice.failedToPay` — submarine swap invoice could not be paid; refund. - */ - case invoiceFailedToPay - /** - * `invoice.expired` — reverse swap invoice expired before payment. - */ - case invoiceExpired - /** - * `transaction.claim.pending` — Boltz ready for a cooperative claim. - */ - case transactionClaimPending - /** - * `transaction.claimed` — onchain funds claimed. - */ - case transactionClaimed - /** - * `transaction.refunded` — onchain funds refunded. - */ - case transactionRefunded - /** - * `transaction.lockupFailed` — wrong amount locked; can refund. - */ - case transactionLockupFailed - /** - * `transaction.failed` — Boltz failed to lock the agreed funds. - */ - case transactionFailed - /** - * `swap.expired` — swap expired without completing. - */ - case swapExpired - /** - * Any status not yet modelled. `raw` holds the verbatim Boltz status. - */ - case unknown(raw: String - ) + +extension BroadcastError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum BtBolt11InvoiceState { + + case pending + case holding + case paid + case canceled } #if compiler(>=6) -extension BoltzSwapStatus: Sendable {} +extension BtBolt11InvoiceState: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBoltzSwapStatus: FfiConverterRustBuffer { - typealias SwiftType = BoltzSwapStatus +public struct FfiConverterTypeBtBolt11InvoiceState: FfiConverterRustBuffer { + typealias SwiftType = BtBolt11InvoiceState - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwapStatus { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtBolt11InvoiceState { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .swapCreated - - case 2: return .invoiceSet - - case 3: return .transactionMempool - - case 4: return .transactionConfirmed - - case 5: return .invoicePending - - case 6: return .invoicePaid - - case 7: return .invoiceSettled - - case 8: return .invoiceFailedToPay - - case 9: return .invoiceExpired - - case 10: return .transactionClaimPending - - case 11: return .transactionClaimed - - case 12: return .transactionRefunded - - case 13: return .transactionLockupFailed + case 1: return .pending - case 14: return .transactionFailed + case 2: return .holding - case 15: return .swapExpired + case 3: return .paid - case 16: return .unknown(raw: try FfiConverterString.read(from: &buf) - ) + case 4: return .canceled default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BoltzSwapStatus, into buf: inout [UInt8]) { + public static func write(_ value: BtBolt11InvoiceState, into buf: inout [UInt8]) { switch value { - case .swapCreated: + case .pending: writeInt(&buf, Int32(1)) - case .invoiceSet: + case .holding: writeInt(&buf, Int32(2)) - case .transactionMempool: + case .paid: writeInt(&buf, Int32(3)) - case .transactionConfirmed: + case .canceled: writeInt(&buf, Int32(4)) + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBtBolt11InvoiceState_lift(_ buf: RustBuffer) throws -> BtBolt11InvoiceState { + return try FfiConverterTypeBtBolt11InvoiceState.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeBtBolt11InvoiceState_lower(_ value: BtBolt11InvoiceState) -> RustBuffer { + return FfiConverterTypeBtBolt11InvoiceState.lower(value) +} + + +extension BtBolt11InvoiceState: Equatable, Hashable {} + +extension BtBolt11InvoiceState: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum BtChannelOrderErrorType { + + case wrongOrderState + case peerNotReachable + case channelRejectedByDestination + case channelRejectedByLsp + case blocktankNotReady +} + + +#if compiler(>=6) +extension BtChannelOrderErrorType: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeBtChannelOrderErrorType: FfiConverterRustBuffer { + typealias SwiftType = BtChannelOrderErrorType + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtChannelOrderErrorType { + let variant: Int32 = try readInt(&buf) + switch variant { - case .invoicePending: - writeInt(&buf, Int32(5)) - - - case .invoicePaid: - writeInt(&buf, Int32(6)) - - - case .invoiceSettled: - writeInt(&buf, Int32(7)) - - - case .invoiceFailedToPay: - writeInt(&buf, Int32(8)) + case 1: return .wrongOrderState + case 2: return .peerNotReachable - case .invoiceExpired: - writeInt(&buf, Int32(9)) + case 3: return .channelRejectedByDestination + case 4: return .channelRejectedByLsp - case .transactionClaimPending: - writeInt(&buf, Int32(10)) + case 5: return .blocktankNotReady + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: BtChannelOrderErrorType, into buf: inout [UInt8]) { + switch value { - case .transactionClaimed: - writeInt(&buf, Int32(11)) + case .wrongOrderState: + writeInt(&buf, Int32(1)) - case .transactionRefunded: - writeInt(&buf, Int32(12)) + case .peerNotReachable: + writeInt(&buf, Int32(2)) - case .transactionLockupFailed: - writeInt(&buf, Int32(13)) + case .channelRejectedByDestination: + writeInt(&buf, Int32(3)) - case .transactionFailed: - writeInt(&buf, Int32(14)) + case .channelRejectedByLsp: + writeInt(&buf, Int32(4)) - case .swapExpired: - writeInt(&buf, Int32(15)) + case .blocktankNotReady: + writeInt(&buf, Int32(5)) - case let .unknown(raw): - writeInt(&buf, Int32(16)) - FfiConverterString.write(raw, into: &buf) - } } } @@ -18524,21 +19890,21 @@ public struct FfiConverterTypeBoltzSwapStatus: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzSwapStatus_lift(_ buf: RustBuffer) throws -> BoltzSwapStatus { - return try FfiConverterTypeBoltzSwapStatus.lift(buf) +public func FfiConverterTypeBtChannelOrderErrorType_lift(_ buf: RustBuffer) throws -> BtChannelOrderErrorType { + return try FfiConverterTypeBtChannelOrderErrorType.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzSwapStatus_lower(_ value: BoltzSwapStatus) -> RustBuffer { - return FfiConverterTypeBoltzSwapStatus.lower(value) +public func FfiConverterTypeBtChannelOrderErrorType_lower(_ value: BtChannelOrderErrorType) -> RustBuffer { + return FfiConverterTypeBtChannelOrderErrorType.lower(value) } -extension BoltzSwapStatus: Equatable, Hashable {} +extension BtChannelOrderErrorType: Equatable, Hashable {} -extension BoltzSwapStatus: Codable {} +extension BtChannelOrderErrorType: Codable {} @@ -18547,55 +19913,54 @@ extension BoltzSwapStatus: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -/** - * The direction of a Boltz swap. - * - * - `Submarine`: onchain Bitcoin -> Lightning (the user locks onchain funds, - * Boltz pays a Lightning invoice). - * - `Reverse`: Lightning -> onchain Bitcoin (the user pays a Boltz hold - * invoice, Boltz locks onchain funds the user then claims). - */ -public enum BoltzSwapType { +public enum BtOpenChannelState { - case submarine - case reverse + case opening + case `open` + case closed } #if compiler(>=6) -extension BoltzSwapType: Sendable {} +extension BtOpenChannelState: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBoltzSwapType: FfiConverterRustBuffer { - typealias SwiftType = BoltzSwapType +public struct FfiConverterTypeBtOpenChannelState: FfiConverterRustBuffer { + typealias SwiftType = BtOpenChannelState - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BoltzSwapType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtOpenChannelState { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .submarine + case 1: return .opening - case 2: return .reverse + case 2: return .`open` + + case 3: return .closed default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BoltzSwapType, into buf: inout [UInt8]) { + public static func write(_ value: BtOpenChannelState, into buf: inout [UInt8]) { switch value { - case .submarine: + case .opening: writeInt(&buf, Int32(1)) - case .reverse: + case .`open`: writeInt(&buf, Int32(2)) + + case .closed: + writeInt(&buf, Int32(3)) + } } } @@ -18604,99 +19969,84 @@ public struct FfiConverterTypeBoltzSwapType: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzSwapType_lift(_ buf: RustBuffer) throws -> BoltzSwapType { - return try FfiConverterTypeBoltzSwapType.lift(buf) +public func FfiConverterTypeBtOpenChannelState_lift(_ buf: RustBuffer) throws -> BtOpenChannelState { + return try FfiConverterTypeBtOpenChannelState.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBoltzSwapType_lower(_ value: BoltzSwapType) -> RustBuffer { - return FfiConverterTypeBoltzSwapType.lower(value) +public func FfiConverterTypeBtOpenChannelState_lower(_ value: BtOpenChannelState) -> RustBuffer { + return FfiConverterTypeBtOpenChannelState.lower(value) } -extension BoltzSwapType: Equatable, Hashable {} - -extension BoltzSwapType: Codable {} - - - +extension BtOpenChannelState: Equatable, Hashable {} +extension BtOpenChannelState: Codable {} -public enum BroadcastError: Swift.Error { - - - case InvalidHex(errorDetails: String - ) - case InvalidTransaction(errorDetails: String - ) - case ElectrumError(errorDetails: String - ) - case TaskError(errorDetails: String - ) + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum BtOrderState { + + case created + case expired + case `open` + case closed } +#if compiler(>=6) +extension BtOrderState: Sendable {} +#endif + #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBroadcastError: FfiConverterRustBuffer { - typealias SwiftType = BroadcastError +public struct FfiConverterTypeBtOrderState: FfiConverterRustBuffer { + typealias SwiftType = BtOrderState - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BroadcastError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtOrderState { let variant: Int32 = try readInt(&buf) switch variant { - - + case 1: return .created - case 1: return .InvalidHex( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 2: return .InvalidTransaction( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 3: return .ElectrumError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 4: return .TaskError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - - default: throw UniffiInternalError.unexpectedEnumCase + case 2: return .expired + + case 3: return .`open` + + case 4: return .closed + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BroadcastError, into buf: inout [UInt8]) { + public static func write(_ value: BtOrderState, into buf: inout [UInt8]) { switch value { - - - - case let .InvalidHex(errorDetails): + case .created: writeInt(&buf, Int32(1)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .InvalidTransaction(errorDetails): + + case .expired: writeInt(&buf, Int32(2)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .ElectrumError(errorDetails): + + case .`open`: writeInt(&buf, Int32(3)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .TaskError(errorDetails): + + case .closed: writeInt(&buf, Int32(4)) - FfiConverterString.write(errorDetails, into: &buf) - + } } } @@ -18705,30 +20055,23 @@ public struct FfiConverterTypeBroadcastError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBroadcastError_lift(_ buf: RustBuffer) throws -> BroadcastError { - return try FfiConverterTypeBroadcastError.lift(buf) +public func FfiConverterTypeBtOrderState_lift(_ buf: RustBuffer) throws -> BtOrderState { + return try FfiConverterTypeBtOrderState.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBroadcastError_lower(_ value: BroadcastError) -> RustBuffer { - return FfiConverterTypeBroadcastError.lower(value) +public func FfiConverterTypeBtOrderState_lower(_ value: BtOrderState) -> RustBuffer { + return FfiConverterTypeBtOrderState.lower(value) } -extension BroadcastError: Equatable, Hashable {} - -extension BroadcastError: Codable {} - +extension BtOrderState: Equatable, Hashable {} +extension BtOrderState: Codable {} -extension BroadcastError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} @@ -18736,58 +20079,58 @@ extension BroadcastError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum BtBolt11InvoiceState { +public enum BtOrderState2 { - case pending - case holding + case created + case expired + case executed case paid - case canceled } #if compiler(>=6) -extension BtBolt11InvoiceState: Sendable {} +extension BtOrderState2: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBtBolt11InvoiceState: FfiConverterRustBuffer { - typealias SwiftType = BtBolt11InvoiceState +public struct FfiConverterTypeBtOrderState2: FfiConverterRustBuffer { + typealias SwiftType = BtOrderState2 - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtBolt11InvoiceState { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtOrderState2 { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .pending + case 1: return .created - case 2: return .holding + case 2: return .expired - case 3: return .paid + case 3: return .executed - case 4: return .canceled + case 4: return .paid default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BtBolt11InvoiceState, into buf: inout [UInt8]) { + public static func write(_ value: BtOrderState2, into buf: inout [UInt8]) { switch value { - case .pending: + case .created: writeInt(&buf, Int32(1)) - case .holding: + case .expired: writeInt(&buf, Int32(2)) - case .paid: + case .executed: writeInt(&buf, Int32(3)) - case .canceled: + case .paid: writeInt(&buf, Int32(4)) } @@ -18798,21 +20141,21 @@ public struct FfiConverterTypeBtBolt11InvoiceState: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtBolt11InvoiceState_lift(_ buf: RustBuffer) throws -> BtBolt11InvoiceState { - return try FfiConverterTypeBtBolt11InvoiceState.lift(buf) +public func FfiConverterTypeBtOrderState2_lift(_ buf: RustBuffer) throws -> BtOrderState2 { + return try FfiConverterTypeBtOrderState2.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtBolt11InvoiceState_lower(_ value: BtBolt11InvoiceState) -> RustBuffer { - return FfiConverterTypeBtBolt11InvoiceState.lower(value) +public func FfiConverterTypeBtOrderState2_lower(_ value: BtOrderState2) -> RustBuffer { + return FfiConverterTypeBtOrderState2.lower(value) } -extension BtBolt11InvoiceState: Equatable, Hashable {} +extension BtOrderState2: Equatable, Hashable {} -extension BtBolt11InvoiceState: Codable {} +extension BtOrderState2: Codable {} @@ -18822,65 +20165,65 @@ extension BtBolt11InvoiceState: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum BtChannelOrderErrorType { +public enum BtPaymentState { - case wrongOrderState - case peerNotReachable - case channelRejectedByDestination - case channelRejectedByLsp - case blocktankNotReady + case created + case partiallyPaid + case paid + case refunded + case refundAvailable } #if compiler(>=6) -extension BtChannelOrderErrorType: Sendable {} +extension BtPaymentState: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBtChannelOrderErrorType: FfiConverterRustBuffer { - typealias SwiftType = BtChannelOrderErrorType +public struct FfiConverterTypeBtPaymentState: FfiConverterRustBuffer { + typealias SwiftType = BtPaymentState - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtChannelOrderErrorType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtPaymentState { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .wrongOrderState + case 1: return .created - case 2: return .peerNotReachable + case 2: return .partiallyPaid - case 3: return .channelRejectedByDestination + case 3: return .paid - case 4: return .channelRejectedByLsp + case 4: return .refunded - case 5: return .blocktankNotReady + case 5: return .refundAvailable default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BtChannelOrderErrorType, into buf: inout [UInt8]) { + public static func write(_ value: BtPaymentState, into buf: inout [UInt8]) { switch value { - case .wrongOrderState: + case .created: writeInt(&buf, Int32(1)) - case .peerNotReachable: + case .partiallyPaid: writeInt(&buf, Int32(2)) - case .channelRejectedByDestination: + case .paid: writeInt(&buf, Int32(3)) - case .channelRejectedByLsp: + case .refunded: writeInt(&buf, Int32(4)) - case .blocktankNotReady: + case .refundAvailable: writeInt(&buf, Int32(5)) } @@ -18891,21 +20234,21 @@ public struct FfiConverterTypeBtChannelOrderErrorType: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtChannelOrderErrorType_lift(_ buf: RustBuffer) throws -> BtChannelOrderErrorType { - return try FfiConverterTypeBtChannelOrderErrorType.lift(buf) +public func FfiConverterTypeBtPaymentState_lift(_ buf: RustBuffer) throws -> BtPaymentState { + return try FfiConverterTypeBtPaymentState.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtChannelOrderErrorType_lower(_ value: BtChannelOrderErrorType) -> RustBuffer { - return FfiConverterTypeBtChannelOrderErrorType.lower(value) +public func FfiConverterTypeBtPaymentState_lower(_ value: BtPaymentState) -> RustBuffer { + return FfiConverterTypeBtPaymentState.lower(value) } -extension BtChannelOrderErrorType: Equatable, Hashable {} +extension BtPaymentState: Equatable, Hashable {} -extension BtChannelOrderErrorType: Codable {} +extension BtPaymentState: Codable {} @@ -18915,53 +20258,67 @@ extension BtChannelOrderErrorType: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum BtOpenChannelState { +public enum BtPaymentState2 { - case opening - case `open` - case closed + case created + case paid + case refunded + case refundAvailable + case canceled } #if compiler(>=6) -extension BtOpenChannelState: Sendable {} +extension BtPaymentState2: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBtOpenChannelState: FfiConverterRustBuffer { - typealias SwiftType = BtOpenChannelState +public struct FfiConverterTypeBtPaymentState2: FfiConverterRustBuffer { + typealias SwiftType = BtPaymentState2 - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtOpenChannelState { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtPaymentState2 { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .opening + case 1: return .created - case 2: return .`open` + case 2: return .paid - case 3: return .closed + case 3: return .refunded + + case 4: return .refundAvailable + + case 5: return .canceled default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BtOpenChannelState, into buf: inout [UInt8]) { + public static func write(_ value: BtPaymentState2, into buf: inout [UInt8]) { switch value { - case .opening: + case .created: writeInt(&buf, Int32(1)) - case .`open`: + case .paid: writeInt(&buf, Int32(2)) - case .closed: + case .refunded: writeInt(&buf, Int32(3)) + + case .refundAvailable: + writeInt(&buf, Int32(4)) + + + case .canceled: + writeInt(&buf, Int32(5)) + } } } @@ -18970,21 +20327,21 @@ public struct FfiConverterTypeBtOpenChannelState: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtOpenChannelState_lift(_ buf: RustBuffer) throws -> BtOpenChannelState { - return try FfiConverterTypeBtOpenChannelState.lift(buf) +public func FfiConverterTypeBtPaymentState2_lift(_ buf: RustBuffer) throws -> BtPaymentState2 { + return try FfiConverterTypeBtPaymentState2.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtOpenChannelState_lower(_ value: BtOpenChannelState) -> RustBuffer { - return FfiConverterTypeBtOpenChannelState.lower(value) +public func FfiConverterTypeBtPaymentState2_lower(_ value: BtPaymentState2) -> RustBuffer { + return FfiConverterTypeBtPaymentState2.lower(value) } -extension BtOpenChannelState: Equatable, Hashable {} +extension BtPaymentState2: Equatable, Hashable {} -extension BtOpenChannelState: Codable {} +extension BtPaymentState2: Codable {} @@ -18994,42 +20351,42 @@ extension BtOpenChannelState: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum BtOrderState { +public enum CJitStateEnum { case created + case completed case expired - case `open` - case closed + case failed } #if compiler(>=6) -extension BtOrderState: Sendable {} +extension CJitStateEnum: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) -#endif -public struct FfiConverterTypeBtOrderState: FfiConverterRustBuffer { - typealias SwiftType = BtOrderState +#endif +public struct FfiConverterTypeCJitStateEnum: FfiConverterRustBuffer { + typealias SwiftType = CJitStateEnum - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtOrderState { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CJitStateEnum { let variant: Int32 = try readInt(&buf) switch variant { case 1: return .created - case 2: return .expired + case 2: return .completed - case 3: return .`open` + case 3: return .expired - case 4: return .closed + case 4: return .failed default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BtOrderState, into buf: inout [UInt8]) { + public static func write(_ value: CJitStateEnum, into buf: inout [UInt8]) { switch value { @@ -19037,15 +20394,15 @@ public struct FfiConverterTypeBtOrderState: FfiConverterRustBuffer { writeInt(&buf, Int32(1)) - case .expired: + case .completed: writeInt(&buf, Int32(2)) - case .`open`: + case .expired: writeInt(&buf, Int32(3)) - case .closed: + case .failed: writeInt(&buf, Int32(4)) } @@ -19056,21 +20413,21 @@ public struct FfiConverterTypeBtOrderState: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtOrderState_lift(_ buf: RustBuffer) throws -> BtOrderState { - return try FfiConverterTypeBtOrderState.lift(buf) +public func FfiConverterTypeCJitStateEnum_lift(_ buf: RustBuffer) throws -> CJitStateEnum { + return try FfiConverterTypeCJitStateEnum.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtOrderState_lower(_ value: BtOrderState) -> RustBuffer { - return FfiConverterTypeBtOrderState.lower(value) +public func FfiConverterTypeCJitStateEnum_lower(_ value: CJitStateEnum) -> RustBuffer { + return FfiConverterTypeCJitStateEnum.lower(value) } -extension BtOrderState: Equatable, Hashable {} +extension CJitStateEnum: Equatable, Hashable {} -extension BtOrderState: Codable {} +extension CJitStateEnum: Codable {} @@ -19079,61 +20436,66 @@ extension BtOrderState: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Coin selection strategy for transaction composition. + */ -public enum BtOrderState2 { +public enum CoinSelection { - case created - case expired - case executed - case paid + /** + * Branch-and-bound (default). Minimizes change by searching for exact matches. + */ + case branchAndBound + /** + * Selects largest UTXOs first. Useful for UTXO consolidation. + */ + case largestFirst + /** + * Selects oldest UTXOs first. Maximizes coin-age spending. + */ + case oldestFirst } #if compiler(>=6) -extension BtOrderState2: Sendable {} +extension CoinSelection: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBtOrderState2: FfiConverterRustBuffer { - typealias SwiftType = BtOrderState2 +public struct FfiConverterTypeCoinSelection: FfiConverterRustBuffer { + typealias SwiftType = CoinSelection - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtOrderState2 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CoinSelection { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .created - - case 2: return .expired + case 1: return .branchAndBound - case 3: return .executed + case 2: return .largestFirst - case 4: return .paid + case 3: return .oldestFirst default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BtOrderState2, into buf: inout [UInt8]) { + public static func write(_ value: CoinSelection, into buf: inout [UInt8]) { switch value { - case .created: + case .branchAndBound: writeInt(&buf, Int32(1)) - case .expired: + case .largestFirst: writeInt(&buf, Int32(2)) - case .executed: + case .oldestFirst: writeInt(&buf, Int32(3)) - - case .paid: - writeInt(&buf, Int32(4)) - } } } @@ -19142,21 +20504,21 @@ public struct FfiConverterTypeBtOrderState2: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtOrderState2_lift(_ buf: RustBuffer) throws -> BtOrderState2 { - return try FfiConverterTypeBtOrderState2.lift(buf) +public func FfiConverterTypeCoinSelection_lift(_ buf: RustBuffer) throws -> CoinSelection { + return try FfiConverterTypeCoinSelection.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtOrderState2_lower(_ value: BtOrderState2) -> RustBuffer { - return FfiConverterTypeBtOrderState2.lower(value) +public func FfiConverterTypeCoinSelection_lower(_ value: CoinSelection) -> RustBuffer { + return FfiConverterTypeCoinSelection.lower(value) } -extension BtOrderState2: Equatable, Hashable {} +extension CoinSelection: Equatable, Hashable {} -extension BtOrderState2: Codable {} +extension CoinSelection: Codable {} @@ -19165,68 +20527,76 @@ extension BtOrderState2: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Output specification for transaction composition. + */ -public enum BtPaymentState { +public enum ComposeOutput { - case created - case partiallyPaid - case paid - case refunded - case refundAvailable + /** + * Payment to a specific address with a fixed amount (satoshis) + */ + case payment(address: String, amountSats: UInt64 + ) + /** + * Send all remaining funds (after fees) to an address + */ + case sendMax(address: String + ) + /** + * OP_RETURN data output (hex-encoded payload) + */ + case opReturn(dataHex: String + ) } #if compiler(>=6) -extension BtPaymentState: Sendable {} +extension ComposeOutput: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBtPaymentState: FfiConverterRustBuffer { - typealias SwiftType = BtPaymentState +public struct FfiConverterTypeComposeOutput: FfiConverterRustBuffer { + typealias SwiftType = ComposeOutput - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtPaymentState { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ComposeOutput { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .created - - case 2: return .partiallyPaid - - case 3: return .paid + case 1: return .payment(address: try FfiConverterString.read(from: &buf), amountSats: try FfiConverterUInt64.read(from: &buf) + ) - case 4: return .refunded + case 2: return .sendMax(address: try FfiConverterString.read(from: &buf) + ) - case 5: return .refundAvailable + case 3: return .opReturn(dataHex: try FfiConverterString.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BtPaymentState, into buf: inout [UInt8]) { + public static func write(_ value: ComposeOutput, into buf: inout [UInt8]) { switch value { - case .created: + case let .payment(address,amountSats): writeInt(&buf, Int32(1)) + FfiConverterString.write(address, into: &buf) + FfiConverterUInt64.write(amountSats, into: &buf) + - - case .partiallyPaid: + case let .sendMax(address): writeInt(&buf, Int32(2)) + FfiConverterString.write(address, into: &buf) + - - case .paid: + case let .opReturn(dataHex): writeInt(&buf, Int32(3)) - - - case .refunded: - writeInt(&buf, Int32(4)) - - - case .refundAvailable: - writeInt(&buf, Int32(5)) - + FfiConverterString.write(dataHex, into: &buf) + } } } @@ -19235,21 +20605,21 @@ public struct FfiConverterTypeBtPaymentState: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtPaymentState_lift(_ buf: RustBuffer) throws -> BtPaymentState { - return try FfiConverterTypeBtPaymentState.lift(buf) +public func FfiConverterTypeComposeOutput_lift(_ buf: RustBuffer) throws -> ComposeOutput { + return try FfiConverterTypeComposeOutput.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtPaymentState_lower(_ value: BtPaymentState) -> RustBuffer { - return FfiConverterTypeBtPaymentState.lower(value) +public func FfiConverterTypeComposeOutput_lower(_ value: ComposeOutput) -> RustBuffer { + return FfiConverterTypeComposeOutput.lower(value) } -extension BtPaymentState: Equatable, Hashable {} +extension ComposeOutput: Equatable, Hashable {} -extension BtPaymentState: Codable {} +extension ComposeOutput: Codable {} @@ -19258,68 +20628,79 @@ extension BtPaymentState: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Result of composing a transaction at a single fee rate. + */ -public enum BtPaymentState2 { +public enum ComposeResult { - case created - case paid - case refunded - case refundAvailable - case canceled + /** + * Successfully built a signable PSBT + */ + case success( + /** + * Base64-encoded PSBT ready for signing + */psbt: String, + /** + * Total fee in satoshis + */fee: UInt64, + /** + * Target fee rate in sat/vB (actual may differ slightly due to rounding) + */feeRate: Float, + /** + * Total value spent (payments + fee, excluding change). + * Uses BDK's `sent - received` semantics, which may undercount for + * self-transfers where the destination is also owned by the wallet. + */totalSpent: UInt64 + ) + /** + * Composition failed (e.g. insufficient funds) + */ + case error(error: String + ) } #if compiler(>=6) -extension BtPaymentState2: Sendable {} +extension ComposeResult: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeBtPaymentState2: FfiConverterRustBuffer { - typealias SwiftType = BtPaymentState2 +public struct FfiConverterTypeComposeResult: FfiConverterRustBuffer { + typealias SwiftType = ComposeResult - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BtPaymentState2 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ComposeResult { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .created - - case 2: return .paid - - case 3: return .refunded - - case 4: return .refundAvailable + case 1: return .success(psbt: try FfiConverterString.read(from: &buf), fee: try FfiConverterUInt64.read(from: &buf), feeRate: try FfiConverterFloat.read(from: &buf), totalSpent: try FfiConverterUInt64.read(from: &buf) + ) - case 5: return .canceled + case 2: return .error(error: try FfiConverterString.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: BtPaymentState2, into buf: inout [UInt8]) { + public static func write(_ value: ComposeResult, into buf: inout [UInt8]) { switch value { - case .created: + case let .success(psbt,fee,feeRate,totalSpent): writeInt(&buf, Int32(1)) + FfiConverterString.write(psbt, into: &buf) + FfiConverterUInt64.write(fee, into: &buf) + FfiConverterFloat.write(feeRate, into: &buf) + FfiConverterUInt64.write(totalSpent, into: &buf) + - - case .paid: + case let .error(error): writeInt(&buf, Int32(2)) - - - case .refunded: - writeInt(&buf, Int32(3)) - - - case .refundAvailable: - writeInt(&buf, Int32(4)) - - - case .canceled: - writeInt(&buf, Int32(5)) - + FfiConverterString.write(error, into: &buf) + } } } @@ -19328,84 +20709,99 @@ public struct FfiConverterTypeBtPaymentState2: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtPaymentState2_lift(_ buf: RustBuffer) throws -> BtPaymentState2 { - return try FfiConverterTypeBtPaymentState2.lift(buf) +public func FfiConverterTypeComposeResult_lift(_ buf: RustBuffer) throws -> ComposeResult { + return try FfiConverterTypeComposeResult.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeBtPaymentState2_lower(_ value: BtPaymentState2) -> RustBuffer { - return FfiConverterTypeBtPaymentState2.lower(value) +public func FfiConverterTypeComposeResult_lower(_ value: ComposeResult) -> RustBuffer { + return FfiConverterTypeComposeResult.lower(value) } -extension BtPaymentState2: Equatable, Hashable {} +extension ComposeResult: Equatable, Hashable {} -extension BtPaymentState2: Codable {} +extension ComposeResult: Codable {} -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum CJitStateEnum { +public enum DbError: Swift.Error { + - case created - case completed - case expired - case failed + + case DbActivityError(errorDetails: ActivityError + ) + case DbBlocktankError(errorDetails: BlocktankError + ) + case DbBoltzError(errorDetails: BoltzError + ) + case InitializationError(errorDetails: String + ) } -#if compiler(>=6) -extension CJitStateEnum: Sendable {} -#endif - #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeCJitStateEnum: FfiConverterRustBuffer { - typealias SwiftType = CJitStateEnum +public struct FfiConverterTypeDbError: FfiConverterRustBuffer { + typealias SwiftType = DbError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CJitStateEnum { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> DbError { let variant: Int32 = try readInt(&buf) switch variant { + - case 1: return .created - - case 2: return .completed - - case 3: return .expired - - case 4: return .failed + - default: throw UniffiInternalError.unexpectedEnumCase + case 1: return .DbActivityError( + errorDetails: try FfiConverterTypeActivityError.read(from: &buf) + ) + case 2: return .DbBlocktankError( + errorDetails: try FfiConverterTypeBlocktankError.read(from: &buf) + ) + case 3: return .DbBoltzError( + errorDetails: try FfiConverterTypeBoltzError.read(from: &buf) + ) + case 4: return .InitializationError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: CJitStateEnum, into buf: inout [UInt8]) { + public static func write(_ value: DbError, into buf: inout [UInt8]) { switch value { + + - case .created: - writeInt(&buf, Int32(1)) + case let .DbActivityError(errorDetails): + writeInt(&buf, Int32(1)) + FfiConverterTypeActivityError.write(errorDetails, into: &buf) + - case .completed: + case let .DbBlocktankError(errorDetails): writeInt(&buf, Int32(2)) + FfiConverterTypeBlocktankError.write(errorDetails, into: &buf) + - - case .expired: + case let .DbBoltzError(errorDetails): writeInt(&buf, Int32(3)) + FfiConverterTypeBoltzError.write(errorDetails, into: &buf) + - - case .failed: + case let .InitializationError(errorDetails): writeInt(&buf, Int32(4)) - + FfiConverterString.write(errorDetails, into: &buf) + } } } @@ -19414,89 +20810,150 @@ public struct FfiConverterTypeCJitStateEnum: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeCJitStateEnum_lift(_ buf: RustBuffer) throws -> CJitStateEnum { - return try FfiConverterTypeCJitStateEnum.lift(buf) +public func FfiConverterTypeDbError_lift(_ buf: RustBuffer) throws -> DbError { + return try FfiConverterTypeDbError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeCJitStateEnum_lower(_ value: CJitStateEnum) -> RustBuffer { - return FfiConverterTypeCJitStateEnum.lower(value) +public func FfiConverterTypeDbError_lower(_ value: DbError) -> RustBuffer { + return FfiConverterTypeDbError.lower(value) } -extension CJitStateEnum: Equatable, Hashable {} +extension DbError: Equatable, Hashable {} -extension CJitStateEnum: Codable {} +extension DbError: Codable {} +extension DbError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -/** - * Coin selection strategy for transaction composition. - */ -public enum CoinSelection { + + +public enum DecodingError: Swift.Error { + - /** - * Branch-and-bound (default). Minimizes change by searching for exact matches. - */ - case branchAndBound - /** - * Selects largest UTXOs first. Useful for UTXO consolidation. - */ - case largestFirst - /** - * Selects oldest UTXOs first. Maximizes coin-age spending. - */ - case oldestFirst + + case InvalidFormat + case InvalidNetwork + case InvalidAmount + case InvalidLnurlPayAmount(amountSatoshis: UInt64, min: UInt64, max: UInt64 + ) + case InvalidTimestamp + case InvalidChecksum + case InvalidResponse + case UnsupportedType + case InvalidAddress + case RequestFailed + case ClientCreationFailed + case InvoiceCreationFailed(errorMessage: String + ) } -#if compiler(>=6) -extension CoinSelection: Sendable {} -#endif - #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeCoinSelection: FfiConverterRustBuffer { - typealias SwiftType = CoinSelection +public struct FfiConverterTypeDecodingError: FfiConverterRustBuffer { + typealias SwiftType = DecodingError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CoinSelection { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> DecodingError { let variant: Int32 = try readInt(&buf) switch variant { + - case 1: return .branchAndBound - - case 2: return .largestFirst - - case 3: return .oldestFirst + - default: throw UniffiInternalError.unexpectedEnumCase + case 1: return .InvalidFormat + case 2: return .InvalidNetwork + case 3: return .InvalidAmount + case 4: return .InvalidLnurlPayAmount( + amountSatoshis: try FfiConverterUInt64.read(from: &buf), + min: try FfiConverterUInt64.read(from: &buf), + max: try FfiConverterUInt64.read(from: &buf) + ) + case 5: return .InvalidTimestamp + case 6: return .InvalidChecksum + case 7: return .InvalidResponse + case 8: return .UnsupportedType + case 9: return .InvalidAddress + case 10: return .RequestFailed + case 11: return .ClientCreationFailed + case 12: return .InvoiceCreationFailed( + errorMessage: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: CoinSelection, into buf: inout [UInt8]) { + public static func write(_ value: DecodingError, into buf: inout [UInt8]) { switch value { + + + - case .branchAndBound: + case .InvalidFormat: writeInt(&buf, Int32(1)) - case .largestFirst: + case .InvalidNetwork: writeInt(&buf, Int32(2)) - case .oldestFirst: + case .InvalidAmount: writeInt(&buf, Int32(3)) + + case let .InvalidLnurlPayAmount(amountSatoshis,min,max): + writeInt(&buf, Int32(4)) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + FfiConverterUInt64.write(min, into: &buf) + FfiConverterUInt64.write(max, into: &buf) + + + case .InvalidTimestamp: + writeInt(&buf, Int32(5)) + + + case .InvalidChecksum: + writeInt(&buf, Int32(6)) + + + case .InvalidResponse: + writeInt(&buf, Int32(7)) + + + case .UnsupportedType: + writeInt(&buf, Int32(8)) + + + case .InvalidAddress: + writeInt(&buf, Int32(9)) + + + case .RequestFailed: + writeInt(&buf, Int32(10)) + + + case .ClientCreationFailed: + writeInt(&buf, Int32(11)) + + + case let .InvoiceCreationFailed(errorMessage): + writeInt(&buf, Int32(12)) + FfiConverterString.write(errorMessage, into: &buf) + } } } @@ -19505,99 +20962,87 @@ public struct FfiConverterTypeCoinSelection: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeCoinSelection_lift(_ buf: RustBuffer) throws -> CoinSelection { - return try FfiConverterTypeCoinSelection.lift(buf) +public func FfiConverterTypeDecodingError_lift(_ buf: RustBuffer) throws -> DecodingError { + return try FfiConverterTypeDecodingError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeCoinSelection_lower(_ value: CoinSelection) -> RustBuffer { - return FfiConverterTypeCoinSelection.lower(value) +public func FfiConverterTypeDecodingError_lower(_ value: DecodingError) -> RustBuffer { + return FfiConverterTypeDecodingError.lower(value) } -extension CoinSelection: Equatable, Hashable {} +extension DecodingError: Equatable, Hashable {} + +extension DecodingError: Codable {} -extension CoinSelection: Codable {} +extension DecodingError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Output specification for transaction composition. + * How an application exchanges data with a hardware wallet. */ -public enum ComposeOutput { +public enum HardwareWalletTransport { - /** - * Payment to a specific address with a fixed amount (satoshis) - */ - case payment(address: String, amountSats: UInt64 - ) - /** - * Send all remaining funds (after fees) to an address - */ - case sendMax(address: String - ) - /** - * OP_RETURN data output (hex-encoded payload) - */ - case opReturn(dataHex: String - ) + case usb + case bluetooth + case qr } #if compiler(>=6) -extension ComposeOutput: Sendable {} +extension HardwareWalletTransport: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeComposeOutput: FfiConverterRustBuffer { - typealias SwiftType = ComposeOutput +public struct FfiConverterTypeHardwareWalletTransport: FfiConverterRustBuffer { + typealias SwiftType = HardwareWalletTransport - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ComposeOutput { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HardwareWalletTransport { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .payment(address: try FfiConverterString.read(from: &buf), amountSats: try FfiConverterUInt64.read(from: &buf) - ) + case 1: return .usb - case 2: return .sendMax(address: try FfiConverterString.read(from: &buf) - ) + case 2: return .bluetooth - case 3: return .opReturn(dataHex: try FfiConverterString.read(from: &buf) - ) + case 3: return .qr default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: ComposeOutput, into buf: inout [UInt8]) { + public static func write(_ value: HardwareWalletTransport, into buf: inout [UInt8]) { switch value { - case let .payment(address,amountSats): + case .usb: writeInt(&buf, Int32(1)) - FfiConverterString.write(address, into: &buf) - FfiConverterUInt64.write(amountSats, into: &buf) - - case let .sendMax(address): + + case .bluetooth: writeInt(&buf, Int32(2)) - FfiConverterString.write(address, into: &buf) - - case let .opReturn(dataHex): + + case .qr: writeInt(&buf, Int32(3)) - FfiConverterString.write(dataHex, into: &buf) - + } } } @@ -19606,21 +21051,21 @@ public struct FfiConverterTypeComposeOutput: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeComposeOutput_lift(_ buf: RustBuffer) throws -> ComposeOutput { - return try FfiConverterTypeComposeOutput.lift(buf) +public func FfiConverterTypeHardwareWalletTransport_lift(_ buf: RustBuffer) throws -> HardwareWalletTransport { + return try FfiConverterTypeHardwareWalletTransport.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeComposeOutput_lower(_ value: ComposeOutput) -> RustBuffer { - return FfiConverterTypeComposeOutput.lower(value) +public func FfiConverterTypeHardwareWalletTransport_lower(_ value: HardwareWalletTransport) -> RustBuffer { + return FfiConverterTypeHardwareWalletTransport.lower(value) } -extension ComposeOutput: Equatable, Hashable {} +extension HardwareWalletTransport: Equatable, Hashable {} -extension ComposeOutput: Codable {} +extension HardwareWalletTransport: Codable {} @@ -19630,78 +21075,56 @@ extension ComposeOutput: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Result of composing a transaction at a single fee rate. + * A hardware-wallet vendor recognized by Bitkit. */ -public enum ComposeResult { +public enum HardwareWalletVendor { - /** - * Successfully built a signable PSBT - */ - case success( - /** - * Base64-encoded PSBT ready for signing - */psbt: String, - /** - * Total fee in satoshis - */fee: UInt64, - /** - * Target fee rate in sat/vB (actual may differ slightly due to rounding) - */feeRate: Float, - /** - * Total value spent (payments + fee, excluding change). - * Uses BDK's `sent - received` semantics, which may undercount for - * self-transfers where the destination is also owned by the wallet. - */totalSpent: UInt64 - ) - /** - * Composition failed (e.g. insufficient funds) - */ - case error(error: String - ) + case trezor + case foundation + case blockstream } #if compiler(>=6) -extension ComposeResult: Sendable {} +extension HardwareWalletVendor: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeComposeResult: FfiConverterRustBuffer { - typealias SwiftType = ComposeResult +public struct FfiConverterTypeHardwareWalletVendor: FfiConverterRustBuffer { + typealias SwiftType = HardwareWalletVendor - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ComposeResult { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HardwareWalletVendor { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .success(psbt: try FfiConverterString.read(from: &buf), fee: try FfiConverterUInt64.read(from: &buf), feeRate: try FfiConverterFloat.read(from: &buf), totalSpent: try FfiConverterUInt64.read(from: &buf) - ) + case 1: return .trezor - case 2: return .error(error: try FfiConverterString.read(from: &buf) - ) + case 2: return .foundation + + case 3: return .blockstream default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: ComposeResult, into buf: inout [UInt8]) { + public static func write(_ value: HardwareWalletVendor, into buf: inout [UInt8]) { switch value { - case let .success(psbt,fee,feeRate,totalSpent): + case .trezor: writeInt(&buf, Int32(1)) - FfiConverterString.write(psbt, into: &buf) - FfiConverterUInt64.write(fee, into: &buf) - FfiConverterFloat.write(feeRate, into: &buf) - FfiConverterUInt64.write(totalSpent, into: &buf) - - case let .error(error): + + case .foundation: writeInt(&buf, Int32(2)) - FfiConverterString.write(error, into: &buf) - + + + case .blockstream: + writeInt(&buf, Int32(3)) + } } } @@ -19710,99 +21133,84 @@ public struct FfiConverterTypeComposeResult: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeComposeResult_lift(_ buf: RustBuffer) throws -> ComposeResult { - return try FfiConverterTypeComposeResult.lift(buf) +public func FfiConverterTypeHardwareWalletVendor_lift(_ buf: RustBuffer) throws -> HardwareWalletVendor { + return try FfiConverterTypeHardwareWalletVendor.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeComposeResult_lower(_ value: ComposeResult) -> RustBuffer { - return FfiConverterTypeComposeResult.lower(value) +public func FfiConverterTypeHardwareWalletVendor_lower(_ value: HardwareWalletVendor) -> RustBuffer { + return FfiConverterTypeHardwareWalletVendor.lower(value) } -extension ComposeResult: Equatable, Hashable {} - -extension ComposeResult: Codable {} +extension HardwareWalletVendor: Equatable, Hashable {} +extension HardwareWalletVendor: Codable {} -public enum DbError: Swift.Error { +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum JadeAddressVariant { - - case DbActivityError(errorDetails: ActivityError - ) - case DbBlocktankError(errorDetails: BlocktankError - ) - case DbBoltzError(errorDetails: BoltzError - ) - case InitializationError(errorDetails: String - ) + case pkh + case wpkh + case shWpkh + case tr } +#if compiler(>=6) +extension JadeAddressVariant: Sendable {} +#endif + #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeDbError: FfiConverterRustBuffer { - typealias SwiftType = DbError +public struct FfiConverterTypeJadeAddressVariant: FfiConverterRustBuffer { + typealias SwiftType = JadeAddressVariant - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> DbError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeAddressVariant { let variant: Int32 = try readInt(&buf) switch variant { - - + case 1: return .pkh - case 1: return .DbActivityError( - errorDetails: try FfiConverterTypeActivityError.read(from: &buf) - ) - case 2: return .DbBlocktankError( - errorDetails: try FfiConverterTypeBlocktankError.read(from: &buf) - ) - case 3: return .DbBoltzError( - errorDetails: try FfiConverterTypeBoltzError.read(from: &buf) - ) - case 4: return .InitializationError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - - default: throw UniffiInternalError.unexpectedEnumCase + case 2: return .wpkh + + case 3: return .shWpkh + + case 4: return .tr + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: DbError, into buf: inout [UInt8]) { + public static func write(_ value: JadeAddressVariant, into buf: inout [UInt8]) { switch value { - - - - case let .DbActivityError(errorDetails): + case .pkh: writeInt(&buf, Int32(1)) - FfiConverterTypeActivityError.write(errorDetails, into: &buf) - - case let .DbBlocktankError(errorDetails): + + case .wpkh: writeInt(&buf, Int32(2)) - FfiConverterTypeBlocktankError.write(errorDetails, into: &buf) - - case let .DbBoltzError(errorDetails): + + case .shWpkh: writeInt(&buf, Int32(3)) - FfiConverterTypeBoltzError.write(errorDetails, into: &buf) - - case let .InitializationError(errorDetails): + + case .tr: writeInt(&buf, Int32(4)) - FfiConverterString.write(errorDetails, into: &buf) - + } } } @@ -19811,52 +21219,68 @@ public struct FfiConverterTypeDbError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeDbError_lift(_ buf: RustBuffer) throws -> DbError { - return try FfiConverterTypeDbError.lift(buf) +public func FfiConverterTypeJadeAddressVariant_lift(_ buf: RustBuffer) throws -> JadeAddressVariant { + return try FfiConverterTypeJadeAddressVariant.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeDbError_lower(_ value: DbError) -> RustBuffer { - return FfiConverterTypeDbError.lower(value) +public func FfiConverterTypeJadeAddressVariant_lower(_ value: JadeAddressVariant) -> RustBuffer { + return FfiConverterTypeJadeAddressVariant.lower(value) } -extension DbError: Equatable, Hashable {} - -extension DbError: Codable {} - +extension JadeAddressVariant: Equatable, Hashable {} +extension JadeAddressVariant: Codable {} -extension DbError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} -public enum DecodingError: Swift.Error { +public enum JadeError: Swift.Error { - case InvalidFormat - case InvalidNetwork - case InvalidAmount - case InvalidLnurlPayAmount(amountSatoshis: UInt64, min: UInt64, max: UInt64 + case TransportError(errorDetails: String + ) + case DeviceNotFound + case DeviceDisconnected + case DeviceBusy + case NotConnected + case NotInitialized + case ConnectionError(errorDetails: String + ) + case ProtocolError(errorDetails: String + ) + case Timeout + case UserCancelled + case DeviceLocked + case DeviceUninitialized + case InvalidPin + case NetworkMismatch(errorDetails: String ) - case InvalidTimestamp - case InvalidChecksum - case InvalidResponse - case UnsupportedType - case InvalidAddress - case RequestFailed - case ClientCreationFailed - case InvoiceCreationFailed(errorMessage: String + case UnsupportedFirmware(installed: String, required: String + ) + case InvalidPath(errorDetails: String + ) + case InvalidPsbt(errorDetails: String + ) + case PsbtTooLarge(size: UInt64, max: UInt64 + ) + case FingerprintMismatch(device: String, psbt: String + ) + case NothingSigned + case AddressMismatch(expected: String, returned: String + ) + case PinServerError(errorDetails: String + ) + case DeviceError(errorDetails: String + ) + case IoError(errorDetails: String ) } @@ -19864,186 +21288,194 @@ public enum DecodingError: Swift.Error { #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeDecodingError: FfiConverterRustBuffer { - typealias SwiftType = DecodingError +public struct FfiConverterTypeJadeError: FfiConverterRustBuffer { + typealias SwiftType = JadeError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> DecodingError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .InvalidFormat - case 2: return .InvalidNetwork - case 3: return .InvalidAmount - case 4: return .InvalidLnurlPayAmount( - amountSatoshis: try FfiConverterUInt64.read(from: &buf), - min: try FfiConverterUInt64.read(from: &buf), + case 1: return .TransportError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .DeviceNotFound + case 3: return .DeviceDisconnected + case 4: return .DeviceBusy + case 5: return .NotConnected + case 6: return .NotInitialized + case 7: return .ConnectionError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 8: return .ProtocolError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 9: return .Timeout + case 10: return .UserCancelled + case 11: return .DeviceLocked + case 12: return .DeviceUninitialized + case 13: return .InvalidPin + case 14: return .NetworkMismatch( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 15: return .UnsupportedFirmware( + installed: try FfiConverterString.read(from: &buf), + required: try FfiConverterString.read(from: &buf) + ) + case 16: return .InvalidPath( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 17: return .InvalidPsbt( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 18: return .PsbtTooLarge( + size: try FfiConverterUInt64.read(from: &buf), max: try FfiConverterUInt64.read(from: &buf) ) - case 5: return .InvalidTimestamp - case 6: return .InvalidChecksum - case 7: return .InvalidResponse - case 8: return .UnsupportedType - case 9: return .InvalidAddress - case 10: return .RequestFailed - case 11: return .ClientCreationFailed - case 12: return .InvoiceCreationFailed( - errorMessage: try FfiConverterString.read(from: &buf) + case 19: return .FingerprintMismatch( + device: try FfiConverterString.read(from: &buf), + psbt: try FfiConverterString.read(from: &buf) + ) + case 20: return .NothingSigned + case 21: return .AddressMismatch( + expected: try FfiConverterString.read(from: &buf), + returned: try FfiConverterString.read(from: &buf) + ) + case 22: return .PinServerError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 23: return .DeviceError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 24: return .IoError( + errorDetails: try FfiConverterString.read(from: &buf) ) default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: DecodingError, into buf: inout [UInt8]) { + public static func write(_ value: JadeError, into buf: inout [UInt8]) { switch value { - case .InvalidFormat: + case let .TransportError(errorDetails): writeInt(&buf, Int32(1)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .InvalidNetwork: + case .DeviceNotFound: writeInt(&buf, Int32(2)) - case .InvalidAmount: + case .DeviceDisconnected: writeInt(&buf, Int32(3)) - case let .InvalidLnurlPayAmount(amountSatoshis,min,max): + case .DeviceBusy: writeInt(&buf, Int32(4)) - FfiConverterUInt64.write(amountSatoshis, into: &buf) - FfiConverterUInt64.write(min, into: &buf) - FfiConverterUInt64.write(max, into: &buf) - - case .InvalidTimestamp: + + case .NotConnected: writeInt(&buf, Int32(5)) - case .InvalidChecksum: + case .NotInitialized: writeInt(&buf, Int32(6)) - case .InvalidResponse: + case let .ConnectionError(errorDetails): writeInt(&buf, Int32(7)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .UnsupportedType: + case let .ProtocolError(errorDetails): writeInt(&buf, Int32(8)) + FfiConverterString.write(errorDetails, into: &buf) + - - case .InvalidAddress: + case .Timeout: writeInt(&buf, Int32(9)) - case .RequestFailed: + case .UserCancelled: writeInt(&buf, Int32(10)) - case .ClientCreationFailed: + case .DeviceLocked: writeInt(&buf, Int32(11)) - case let .InvoiceCreationFailed(errorMessage): + case .DeviceUninitialized: writeInt(&buf, Int32(12)) - FfiConverterString.write(errorMessage, into: &buf) - - } - } -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeDecodingError_lift(_ buf: RustBuffer) throws -> DecodingError { - return try FfiConverterTypeDecodingError.lift(buf) -} - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeDecodingError_lower(_ value: DecodingError) -> RustBuffer { - return FfiConverterTypeDecodingError.lower(value) -} - - -extension DecodingError: Equatable, Hashable {} - -extension DecodingError: Codable {} - - - - -extension DecodingError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} - - - - -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -/** - * How an application exchanges data with a hardware wallet. - */ - -public enum HardwareWalletTransport { - - case usb - case bluetooth - case qr -} - - -#if compiler(>=6) -extension HardwareWalletTransport: Sendable {} -#endif - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeHardwareWalletTransport: FfiConverterRustBuffer { - typealias SwiftType = HardwareWalletTransport - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HardwareWalletTransport { - let variant: Int32 = try readInt(&buf) - switch variant { - case 1: return .usb - case 2: return .bluetooth + case .InvalidPin: + writeInt(&buf, Int32(13)) - case 3: return .qr - default: throw UniffiInternalError.unexpectedEnumCase - } - } - - public static func write(_ value: HardwareWalletTransport, into buf: inout [UInt8]) { - switch value { + case let .NetworkMismatch(errorDetails): + writeInt(&buf, Int32(14)) + FfiConverterString.write(errorDetails, into: &buf) + + case let .UnsupportedFirmware(installed,required): + writeInt(&buf, Int32(15)) + FfiConverterString.write(installed, into: &buf) + FfiConverterString.write(required, into: &buf) + - case .usb: - writeInt(&buf, Int32(1)) + case let .InvalidPath(errorDetails): + writeInt(&buf, Int32(16)) + FfiConverterString.write(errorDetails, into: &buf) + + case let .InvalidPsbt(errorDetails): + writeInt(&buf, Int32(17)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .PsbtTooLarge(size,max): + writeInt(&buf, Int32(18)) + FfiConverterUInt64.write(size, into: &buf) + FfiConverterUInt64.write(max, into: &buf) + + + case let .FingerprintMismatch(device,psbt): + writeInt(&buf, Int32(19)) + FfiConverterString.write(device, into: &buf) + FfiConverterString.write(psbt, into: &buf) + + + case .NothingSigned: + writeInt(&buf, Int32(20)) - case .bluetooth: - writeInt(&buf, Int32(2)) + case let .AddressMismatch(expected,returned): + writeInt(&buf, Int32(21)) + FfiConverterString.write(expected, into: &buf) + FfiConverterString.write(returned, into: &buf) + - case .qr: - writeInt(&buf, Int32(3)) + case let .PinServerError(errorDetails): + writeInt(&buf, Int32(22)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .DeviceError(errorDetails): + writeInt(&buf, Int32(23)) + FfiConverterString.write(errorDetails, into: &buf) + + case let .IoError(errorDetails): + writeInt(&buf, Int32(24)) + FfiConverterString.write(errorDetails, into: &buf) + } } } @@ -20052,78 +21484,82 @@ public struct FfiConverterTypeHardwareWalletTransport: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeHardwareWalletTransport_lift(_ buf: RustBuffer) throws -> HardwareWalletTransport { - return try FfiConverterTypeHardwareWalletTransport.lift(buf) +public func FfiConverterTypeJadeError_lift(_ buf: RustBuffer) throws -> JadeError { + return try FfiConverterTypeJadeError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeHardwareWalletTransport_lower(_ value: HardwareWalletTransport) -> RustBuffer { - return FfiConverterTypeHardwareWalletTransport.lower(value) +public func FfiConverterTypeJadeError_lower(_ value: JadeError) -> RustBuffer { + return FfiConverterTypeJadeError.lower(value) } -extension HardwareWalletTransport: Equatable, Hashable {} +extension JadeError: Equatable, Hashable {} -extension HardwareWalletTransport: Codable {} +extension JadeError: Codable {} +extension JadeError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -/** - * A hardware-wallet vendor recognized by Bitkit. - */ -public enum HardwareWalletVendor { +public enum JadeNetwork { - case trezor - case foundation - case blockstream + case mainnet + case testnet + case regtest } #if compiler(>=6) -extension HardwareWalletVendor: Sendable {} +extension JadeNetwork: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeHardwareWalletVendor: FfiConverterRustBuffer { - typealias SwiftType = HardwareWalletVendor +public struct FfiConverterTypeJadeNetwork: FfiConverterRustBuffer { + typealias SwiftType = JadeNetwork - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HardwareWalletVendor { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeNetwork { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .trezor + case 1: return .mainnet - case 2: return .foundation + case 2: return .testnet - case 3: return .blockstream + case 3: return .regtest default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: HardwareWalletVendor, into buf: inout [UInt8]) { + public static func write(_ value: JadeNetwork, into buf: inout [UInt8]) { switch value { - case .trezor: + case .mainnet: writeInt(&buf, Int32(1)) - case .foundation: + case .testnet: writeInt(&buf, Int32(2)) - case .blockstream: + case .regtest: writeInt(&buf, Int32(3)) } @@ -20134,21 +21570,21 @@ public struct FfiConverterTypeHardwareWalletVendor: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeHardwareWalletVendor_lift(_ buf: RustBuffer) throws -> HardwareWalletVendor { - return try FfiConverterTypeHardwareWalletVendor.lift(buf) +public func FfiConverterTypeJadeNetwork_lift(_ buf: RustBuffer) throws -> JadeNetwork { + return try FfiConverterTypeJadeNetwork.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeHardwareWalletVendor_lower(_ value: HardwareWalletVendor) -> RustBuffer { - return FfiConverterTypeHardwareWalletVendor.lower(value) +public func FfiConverterTypeJadeNetwork_lower(_ value: JadeNetwork) -> RustBuffer { + return FfiConverterTypeJadeNetwork.lower(value) } -extension HardwareWalletVendor: Equatable, Hashable {} +extension JadeNetwork: Equatable, Hashable {} -extension HardwareWalletVendor: Codable {} +extension JadeNetwork: Codable {} @@ -20158,60 +21594,53 @@ extension HardwareWalletVendor: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum JadeAddressVariant { +public enum JadePingStatus { - case pkh - case wpkh - case shWpkh - case tr + case idle + case busy + case awaitingUserInput } #if compiler(>=6) -extension JadeAddressVariant: Sendable {} +extension JadePingStatus: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeJadeAddressVariant: FfiConverterRustBuffer { - typealias SwiftType = JadeAddressVariant +public struct FfiConverterTypeJadePingStatus: FfiConverterRustBuffer { + typealias SwiftType = JadePingStatus - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeAddressVariant { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadePingStatus { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .pkh - - case 2: return .wpkh + case 1: return .idle - case 3: return .shWpkh + case 2: return .busy - case 4: return .tr + case 3: return .awaitingUserInput default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: JadeAddressVariant, into buf: inout [UInt8]) { + public static func write(_ value: JadePingStatus, into buf: inout [UInt8]) { switch value { - case .pkh: + case .idle: writeInt(&buf, Int32(1)) - case .wpkh: + case .busy: writeInt(&buf, Int32(2)) - case .shWpkh: + case .awaitingUserInput: writeInt(&buf, Int32(3)) - - case .tr: - writeInt(&buf, Int32(4)) - } } } @@ -20220,263 +21649,191 @@ public struct FfiConverterTypeJadeAddressVariant: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeAddressVariant_lift(_ buf: RustBuffer) throws -> JadeAddressVariant { - return try FfiConverterTypeJadeAddressVariant.lift(buf) +public func FfiConverterTypeJadePingStatus_lift(_ buf: RustBuffer) throws -> JadePingStatus { + return try FfiConverterTypeJadePingStatus.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeAddressVariant_lower(_ value: JadeAddressVariant) -> RustBuffer { - return FfiConverterTypeJadeAddressVariant.lower(value) +public func FfiConverterTypeJadePingStatus_lower(_ value: JadePingStatus) -> RustBuffer { + return FfiConverterTypeJadePingStatus.lower(value) } -extension JadeAddressVariant: Equatable, Hashable {} - -extension JadeAddressVariant: Codable {} +extension JadePingStatus: Equatable, Hashable {} +extension JadePingStatus: Codable {} -public enum JadeError: Swift.Error { +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum JadeState { - - case TransportError(errorDetails: String - ) - case DeviceNotFound - case DeviceDisconnected - case DeviceBusy - case NotConnected - case NotInitialized - case ConnectionError(errorDetails: String - ) - case ProtocolError(errorDetails: String - ) - case Timeout - case UserCancelled - case DeviceLocked - case DeviceUninitialized - case InvalidPin - case NetworkMismatch(errorDetails: String - ) - case UnsupportedFirmware(installed: String, required: String - ) - case InvalidPath(errorDetails: String - ) - case InvalidPsbt(errorDetails: String - ) - case PsbtTooLarge(size: UInt64, max: UInt64 - ) - case FingerprintMismatch(device: String, psbt: String - ) - case NothingSigned - case AddressMismatch(expected: String, returned: String - ) - case PinServerError(errorDetails: String - ) - case DeviceError(errorDetails: String - ) - case IoError(errorDetails: String - ) + case uninit + case unsaved + case locked + case ready + case temp + case unknown } +#if compiler(>=6) +extension JadeState: Sendable {} +#endif + #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeJadeError: FfiConverterRustBuffer { - typealias SwiftType = JadeError +public struct FfiConverterTypeJadeState: FfiConverterRustBuffer { + typealias SwiftType = JadeState - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeState { let variant: Int32 = try readInt(&buf) switch variant { - - + case 1: return .uninit - case 1: return .TransportError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 2: return .DeviceNotFound - case 3: return .DeviceDisconnected - case 4: return .DeviceBusy - case 5: return .NotConnected - case 6: return .NotInitialized - case 7: return .ConnectionError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 8: return .ProtocolError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 9: return .Timeout - case 10: return .UserCancelled - case 11: return .DeviceLocked - case 12: return .DeviceUninitialized - case 13: return .InvalidPin - case 14: return .NetworkMismatch( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 15: return .UnsupportedFirmware( - installed: try FfiConverterString.read(from: &buf), - required: try FfiConverterString.read(from: &buf) - ) - case 16: return .InvalidPath( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 17: return .InvalidPsbt( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 18: return .PsbtTooLarge( - size: try FfiConverterUInt64.read(from: &buf), - max: try FfiConverterUInt64.read(from: &buf) - ) - case 19: return .FingerprintMismatch( - device: try FfiConverterString.read(from: &buf), - psbt: try FfiConverterString.read(from: &buf) - ) - case 20: return .NothingSigned - case 21: return .AddressMismatch( - expected: try FfiConverterString.read(from: &buf), - returned: try FfiConverterString.read(from: &buf) - ) - case 22: return .PinServerError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 23: return .DeviceError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 24: return .IoError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - - default: throw UniffiInternalError.unexpectedEnumCase + case 2: return .unsaved + + case 3: return .locked + + case 4: return .ready + + case 5: return .temp + + case 6: return .unknown + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: JadeError, into buf: inout [UInt8]) { + public static func write(_ value: JadeState, into buf: inout [UInt8]) { switch value { - - - - case let .TransportError(errorDetails): + case .uninit: writeInt(&buf, Int32(1)) - FfiConverterString.write(errorDetails, into: &buf) - - case .DeviceNotFound: + + case .unsaved: writeInt(&buf, Int32(2)) - case .DeviceDisconnected: + case .locked: writeInt(&buf, Int32(3)) - case .DeviceBusy: + case .ready: writeInt(&buf, Int32(4)) - case .NotConnected: + case .temp: writeInt(&buf, Int32(5)) - case .NotInitialized: + case .unknown: writeInt(&buf, Int32(6)) + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeJadeState_lift(_ buf: RustBuffer) throws -> JadeState { + return try FfiConverterTypeJadeState.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeJadeState_lower(_ value: JadeState) -> RustBuffer { + return FfiConverterTypeJadeState.lower(value) +} + + +extension JadeState: Equatable, Hashable {} + +extension JadeState: Codable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum JadeTransportErrorCode { + + case deviceBusy + case notConnected + case disconnected + case timeout + case permissionDenied +} + + +#if compiler(>=6) +extension JadeTransportErrorCode: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeJadeTransportErrorCode: FfiConverterRustBuffer { + typealias SwiftType = JadeTransportErrorCode + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeTransportErrorCode { + let variant: Int32 = try readInt(&buf) + switch variant { - case let .ConnectionError(errorDetails): - writeInt(&buf, Int32(7)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .ProtocolError(errorDetails): - writeInt(&buf, Int32(8)) - FfiConverterString.write(errorDetails, into: &buf) - - - case .Timeout: - writeInt(&buf, Int32(9)) - - - case .UserCancelled: - writeInt(&buf, Int32(10)) - - - case .DeviceLocked: - writeInt(&buf, Int32(11)) - + case 1: return .deviceBusy - case .DeviceUninitialized: - writeInt(&buf, Int32(12)) + case 2: return .notConnected + case 3: return .disconnected - case .InvalidPin: - writeInt(&buf, Int32(13)) + case 4: return .timeout + case 5: return .permissionDenied - case let .NetworkMismatch(errorDetails): - writeInt(&buf, Int32(14)) - FfiConverterString.write(errorDetails, into: &buf) - + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: JadeTransportErrorCode, into buf: inout [UInt8]) { + switch value { - case let .UnsupportedFirmware(installed,required): - writeInt(&buf, Int32(15)) - FfiConverterString.write(installed, into: &buf) - FfiConverterString.write(required, into: &buf) - - case let .InvalidPath(errorDetails): - writeInt(&buf, Int32(16)) - FfiConverterString.write(errorDetails, into: &buf) - + case .deviceBusy: + writeInt(&buf, Int32(1)) - case let .InvalidPsbt(errorDetails): - writeInt(&buf, Int32(17)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .PsbtTooLarge(size,max): - writeInt(&buf, Int32(18)) - FfiConverterUInt64.write(size, into: &buf) - FfiConverterUInt64.write(max, into: &buf) - + case .notConnected: + writeInt(&buf, Int32(2)) - case let .FingerprintMismatch(device,psbt): - writeInt(&buf, Int32(19)) - FfiConverterString.write(device, into: &buf) - FfiConverterString.write(psbt, into: &buf) - - case .NothingSigned: - writeInt(&buf, Int32(20)) + case .disconnected: + writeInt(&buf, Int32(3)) - case let .AddressMismatch(expected,returned): - writeInt(&buf, Int32(21)) - FfiConverterString.write(expected, into: &buf) - FfiConverterString.write(returned, into: &buf) - + case .timeout: + writeInt(&buf, Int32(4)) - case let .PinServerError(errorDetails): - writeInt(&buf, Int32(22)) - FfiConverterString.write(errorDetails, into: &buf) - - case let .DeviceError(errorDetails): - writeInt(&buf, Int32(23)) - FfiConverterString.write(errorDetails, into: &buf) - + case .permissionDenied: + writeInt(&buf, Int32(5)) - case let .IoError(errorDetails): - writeInt(&buf, Int32(24)) - FfiConverterString.write(errorDetails, into: &buf) - } } } @@ -20485,30 +21842,23 @@ public struct FfiConverterTypeJadeError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeError_lift(_ buf: RustBuffer) throws -> JadeError { - return try FfiConverterTypeJadeError.lift(buf) +public func FfiConverterTypeJadeTransportErrorCode_lift(_ buf: RustBuffer) throws -> JadeTransportErrorCode { + return try FfiConverterTypeJadeTransportErrorCode.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeError_lower(_ value: JadeError) -> RustBuffer { - return FfiConverterTypeJadeError.lower(value) +public func FfiConverterTypeJadeTransportErrorCode_lower(_ value: JadeTransportErrorCode) -> RustBuffer { + return FfiConverterTypeJadeTransportErrorCode.lower(value) } -extension JadeError: Equatable, Hashable {} - -extension JadeError: Codable {} - +extension JadeTransportErrorCode: Equatable, Hashable {} +extension JadeTransportErrorCode: Codable {} -extension JadeError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} @@ -20516,53 +21866,46 @@ extension JadeError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum JadeNetwork { +public enum JadeTransportKind { - case mainnet - case testnet - case regtest + case bluetooth + case serial } #if compiler(>=6) -extension JadeNetwork: Sendable {} +extension JadeTransportKind: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeJadeNetwork: FfiConverterRustBuffer { - typealias SwiftType = JadeNetwork +public struct FfiConverterTypeJadeTransportKind: FfiConverterRustBuffer { + typealias SwiftType = JadeTransportKind - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeNetwork { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeTransportKind { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .mainnet - - case 2: return .testnet + case 1: return .bluetooth - case 3: return .regtest + case 2: return .serial default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: JadeNetwork, into buf: inout [UInt8]) { + public static func write(_ value: JadeTransportKind, into buf: inout [UInt8]) { switch value { - case .mainnet: + case .bluetooth: writeInt(&buf, Int32(1)) - case .testnet: + case .serial: writeInt(&buf, Int32(2)) - - case .regtest: - writeInt(&buf, Int32(3)) - } } } @@ -20571,77 +21914,125 @@ public struct FfiConverterTypeJadeNetwork: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeNetwork_lift(_ buf: RustBuffer) throws -> JadeNetwork { - return try FfiConverterTypeJadeNetwork.lift(buf) +public func FfiConverterTypeJadeTransportKind_lift(_ buf: RustBuffer) throws -> JadeTransportKind { + return try FfiConverterTypeJadeTransportKind.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeNetwork_lower(_ value: JadeNetwork) -> RustBuffer { - return FfiConverterTypeJadeNetwork.lower(value) +public func FfiConverterTypeJadeTransportKind_lower(_ value: JadeTransportKind) -> RustBuffer { + return FfiConverterTypeJadeTransportKind.lower(value) } -extension JadeNetwork: Equatable, Hashable {} +extension JadeTransportKind: Equatable, Hashable {} -extension JadeNetwork: Codable {} +extension JadeTransportKind: Codable {} -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum JadePingStatus { +public enum LnurlError: Swift.Error { + - case idle - case busy - case awaitingUserInput + + case InvalidAddress + case ClientCreationFailed + case RequestFailed + case InvalidResponse + case InvalidAmount(amountSatoshis: UInt64, min: UInt64, max: UInt64 + ) + case InvoiceCreationFailed(errorDetails: String + ) + case AmountMismatch(requestedMsats: UInt64, invoiceMsats: UInt64 + ) + case AuthenticationFailed } -#if compiler(>=6) -extension JadePingStatus: Sendable {} -#endif - #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeJadePingStatus: FfiConverterRustBuffer { - typealias SwiftType = JadePingStatus +public struct FfiConverterTypeLnurlError: FfiConverterRustBuffer { + typealias SwiftType = LnurlError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadePingStatus { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> LnurlError { let variant: Int32 = try readInt(&buf) switch variant { + - case 1: return .idle - - case 2: return .busy - - case 3: return .awaitingUserInput + - default: throw UniffiInternalError.unexpectedEnumCase + case 1: return .InvalidAddress + case 2: return .ClientCreationFailed + case 3: return .RequestFailed + case 4: return .InvalidResponse + case 5: return .InvalidAmount( + amountSatoshis: try FfiConverterUInt64.read(from: &buf), + min: try FfiConverterUInt64.read(from: &buf), + max: try FfiConverterUInt64.read(from: &buf) + ) + case 6: return .InvoiceCreationFailed( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 7: return .AmountMismatch( + requestedMsats: try FfiConverterUInt64.read(from: &buf), + invoiceMsats: try FfiConverterUInt64.read(from: &buf) + ) + case 8: return .AuthenticationFailed + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: JadePingStatus, into buf: inout [UInt8]) { + public static func write(_ value: LnurlError, into buf: inout [UInt8]) { switch value { + + + - case .idle: + case .InvalidAddress: writeInt(&buf, Int32(1)) - case .busy: + case .ClientCreationFailed: writeInt(&buf, Int32(2)) - case .awaitingUserInput: + case .RequestFailed: writeInt(&buf, Int32(3)) + + case .InvalidResponse: + writeInt(&buf, Int32(4)) + + + case let .InvalidAmount(amountSatoshis,min,max): + writeInt(&buf, Int32(5)) + FfiConverterUInt64.write(amountSatoshis, into: &buf) + FfiConverterUInt64.write(min, into: &buf) + FfiConverterUInt64.write(max, into: &buf) + + + case let .InvoiceCreationFailed(errorDetails): + writeInt(&buf, Int32(6)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .AmountMismatch(requestedMsats,invoiceMsats): + writeInt(&buf, Int32(7)) + FfiConverterUInt64.write(requestedMsats, into: &buf) + FfiConverterUInt64.write(invoiceMsats, into: &buf) + + + case .AuthenticationFailed: + writeInt(&buf, Int32(8)) + } } } @@ -20650,98 +22041,91 @@ public struct FfiConverterTypeJadePingStatus: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadePingStatus_lift(_ buf: RustBuffer) throws -> JadePingStatus { - return try FfiConverterTypeJadePingStatus.lift(buf) +public func FfiConverterTypeLnurlError_lift(_ buf: RustBuffer) throws -> LnurlError { + return try FfiConverterTypeLnurlError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadePingStatus_lower(_ value: JadePingStatus) -> RustBuffer { - return FfiConverterTypeJadePingStatus.lower(value) +public func FfiConverterTypeLnurlError_lower(_ value: LnurlError) -> RustBuffer { + return FfiConverterTypeLnurlError.lower(value) } -extension JadePingStatus: Equatable, Hashable {} +extension LnurlError: Equatable, Hashable {} + +extension LnurlError: Codable {} -extension JadePingStatus: Codable {} +extension LnurlError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum JadeState { +public enum ManualRefundStateEnum { - case uninit - case unsaved - case locked - case ready - case temp - case unknown + case created + case approved + case rejected + case sent } #if compiler(>=6) -extension JadeState: Sendable {} +extension ManualRefundStateEnum: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeJadeState: FfiConverterRustBuffer { - typealias SwiftType = JadeState +public struct FfiConverterTypeManualRefundStateEnum: FfiConverterRustBuffer { + typealias SwiftType = ManualRefundStateEnum - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeState { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ManualRefundStateEnum { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .uninit - - case 2: return .unsaved - - case 3: return .locked + case 1: return .created - case 4: return .ready + case 2: return .approved - case 5: return .temp + case 3: return .rejected - case 6: return .unknown + case 4: return .sent default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: JadeState, into buf: inout [UInt8]) { + public static func write(_ value: ManualRefundStateEnum, into buf: inout [UInt8]) { switch value { - case .uninit: + case .created: writeInt(&buf, Int32(1)) - case .unsaved: + case .approved: writeInt(&buf, Int32(2)) - case .locked: + case .rejected: writeInt(&buf, Int32(3)) - case .ready: + case .sent: writeInt(&buf, Int32(4)) - - case .temp: - writeInt(&buf, Int32(5)) - - - case .unknown: - writeInt(&buf, Int32(6)) - } } } @@ -20750,21 +22134,21 @@ public struct FfiConverterTypeJadeState: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeState_lift(_ buf: RustBuffer) throws -> JadeState { - return try FfiConverterTypeJadeState.lift(buf) +public func FfiConverterTypeManualRefundStateEnum_lift(_ buf: RustBuffer) throws -> ManualRefundStateEnum { + return try FfiConverterTypeManualRefundStateEnum.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeState_lower(_ value: JadeState) -> RustBuffer { - return FfiConverterTypeJadeState.lower(value) +public func FfiConverterTypeManualRefundStateEnum_lower(_ value: ManualRefundStateEnum) -> RustBuffer { + return FfiConverterTypeManualRefundStateEnum.lower(value) } -extension JadeState: Equatable, Hashable {} +extension ManualRefundStateEnum: Equatable, Hashable {} -extension JadeState: Codable {} +extension ManualRefundStateEnum: Codable {} @@ -20774,65 +22158,80 @@ extension JadeState: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum JadeTransportErrorCode { +public enum Network { - case deviceBusy - case notConnected - case disconnected - case timeout - case permissionDenied + /** + * Mainnet Bitcoin. + */ + case bitcoin + /** + * Bitcoin's testnet network. + */ + case testnet + /** + * Bitcoin's testnet4 network. + */ + case testnet4 + /** + * Bitcoin's signet network. + */ + case signet + /** + * Bitcoin's regtest network. + */ + case regtest } #if compiler(>=6) -extension JadeTransportErrorCode: Sendable {} +extension Network: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeJadeTransportErrorCode: FfiConverterRustBuffer { - typealias SwiftType = JadeTransportErrorCode +public struct FfiConverterTypeNetwork: FfiConverterRustBuffer { + typealias SwiftType = Network - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeTransportErrorCode { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Network { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .deviceBusy + case 1: return .bitcoin - case 2: return .notConnected + case 2: return .testnet - case 3: return .disconnected + case 3: return .testnet4 - case 4: return .timeout + case 4: return .signet - case 5: return .permissionDenied + case 5: return .regtest default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: JadeTransportErrorCode, into buf: inout [UInt8]) { + public static func write(_ value: Network, into buf: inout [UInt8]) { switch value { - case .deviceBusy: + case .bitcoin: writeInt(&buf, Int32(1)) - case .notConnected: + case .testnet: writeInt(&buf, Int32(2)) - case .disconnected: + case .testnet4: writeInt(&buf, Int32(3)) - case .timeout: + case .signet: writeInt(&buf, Int32(4)) - case .permissionDenied: + case .regtest: writeInt(&buf, Int32(5)) } @@ -20843,21 +22242,21 @@ public struct FfiConverterTypeJadeTransportErrorCode: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeTransportErrorCode_lift(_ buf: RustBuffer) throws -> JadeTransportErrorCode { - return try FfiConverterTypeJadeTransportErrorCode.lift(buf) +public func FfiConverterTypeNetwork_lift(_ buf: RustBuffer) throws -> Network { + return try FfiConverterTypeNetwork.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeTransportErrorCode_lower(_ value: JadeTransportErrorCode) -> RustBuffer { - return FfiConverterTypeJadeTransportErrorCode.lower(value) +public func FfiConverterTypeNetwork_lower(_ value: Network) -> RustBuffer { + return FfiConverterTypeNetwork.lower(value) } -extension JadeTransportErrorCode: Equatable, Hashable {} +extension Network: Equatable, Hashable {} -extension JadeTransportErrorCode: Codable {} +extension Network: Codable {} @@ -20867,45 +22266,59 @@ extension JadeTransportErrorCode: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum JadeTransportKind { +public enum NetworkType { - case bluetooth - case serial + case bitcoin + case testnet + case regtest + case signet } #if compiler(>=6) -extension JadeTransportKind: Sendable {} +extension NetworkType: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeJadeTransportKind: FfiConverterRustBuffer { - typealias SwiftType = JadeTransportKind +public struct FfiConverterTypeNetworkType: FfiConverterRustBuffer { + typealias SwiftType = NetworkType - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> JadeTransportKind { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> NetworkType { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .bluetooth + case 1: return .bitcoin - case 2: return .serial + case 2: return .testnet + + case 3: return .regtest + + case 4: return .signet default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: JadeTransportKind, into buf: inout [UInt8]) { + public static func write(_ value: NetworkType, into buf: inout [UInt8]) { switch value { - case .bluetooth: + case .bitcoin: writeInt(&buf, Int32(1)) - case .serial: - writeInt(&buf, Int32(2)) + case .testnet: + writeInt(&buf, Int32(2)) + + + case .regtest: + writeInt(&buf, Int32(3)) + + + case .signet: + writeInt(&buf, Int32(4)) } } @@ -20915,21 +22328,21 @@ public struct FfiConverterTypeJadeTransportKind: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeTransportKind_lift(_ buf: RustBuffer) throws -> JadeTransportKind { - return try FfiConverterTypeJadeTransportKind.lift(buf) +public func FfiConverterTypeNetworkType_lift(_ buf: RustBuffer) throws -> NetworkType { + return try FfiConverterTypeNetworkType.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeJadeTransportKind_lower(_ value: JadeTransportKind) -> RustBuffer { - return FfiConverterTypeJadeTransportKind.lower(value) +public func FfiConverterTypeNetworkType_lower(_ value: NetworkType) -> RustBuffer { + return FfiConverterTypeNetworkType.lower(value) } -extension JadeTransportKind: Equatable, Hashable {} +extension NetworkType: Equatable, Hashable {} -extension JadeTransportKind: Codable {} +extension NetworkType: Codable {} @@ -20937,103 +22350,47 @@ extension JadeTransportKind: Codable {} -public enum LnurlError: Swift.Error { +public enum OnchainError: Swift.Error { - case InvalidAddress - case ClientCreationFailed - case RequestFailed - case InvalidResponse - case InvalidAmount(amountSatoshis: UInt64, min: UInt64, max: UInt64 - ) - case InvoiceCreationFailed(errorDetails: String - ) - case AmountMismatch(requestedMsats: UInt64, invoiceMsats: UInt64 + case InvalidExtendedPublicKey(errorDetails: String ) - case AuthenticationFailed } #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeLnurlError: FfiConverterRustBuffer { - typealias SwiftType = LnurlError +public struct FfiConverterTypeOnchainError: FfiConverterRustBuffer { + typealias SwiftType = OnchainError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> LnurlError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> OnchainError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .InvalidAddress - case 2: return .ClientCreationFailed - case 3: return .RequestFailed - case 4: return .InvalidResponse - case 5: return .InvalidAmount( - amountSatoshis: try FfiConverterUInt64.read(from: &buf), - min: try FfiConverterUInt64.read(from: &buf), - max: try FfiConverterUInt64.read(from: &buf) - ) - case 6: return .InvoiceCreationFailed( + case 1: return .InvalidExtendedPublicKey( errorDetails: try FfiConverterString.read(from: &buf) ) - case 7: return .AmountMismatch( - requestedMsats: try FfiConverterUInt64.read(from: &buf), - invoiceMsats: try FfiConverterUInt64.read(from: &buf) - ) - case 8: return .AuthenticationFailed default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: LnurlError, into buf: inout [UInt8]) { + public static func write(_ value: OnchainError, into buf: inout [UInt8]) { switch value { - case .InvalidAddress: + case let .InvalidExtendedPublicKey(errorDetails): writeInt(&buf, Int32(1)) - - - case .ClientCreationFailed: - writeInt(&buf, Int32(2)) - - - case .RequestFailed: - writeInt(&buf, Int32(3)) - - - case .InvalidResponse: - writeInt(&buf, Int32(4)) - - - case let .InvalidAmount(amountSatoshis,min,max): - writeInt(&buf, Int32(5)) - FfiConverterUInt64.write(amountSatoshis, into: &buf) - FfiConverterUInt64.write(min, into: &buf) - FfiConverterUInt64.write(max, into: &buf) - - - case let .InvoiceCreationFailed(errorDetails): - writeInt(&buf, Int32(6)) FfiConverterString.write(errorDetails, into: &buf) - - case let .AmountMismatch(requestedMsats,invoiceMsats): - writeInt(&buf, Int32(7)) - FfiConverterUInt64.write(requestedMsats, into: &buf) - FfiConverterUInt64.write(invoiceMsats, into: &buf) - - - case .AuthenticationFailed: - writeInt(&buf, Int32(8)) - } } } @@ -21042,26 +22399,26 @@ public struct FfiConverterTypeLnurlError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeLnurlError_lift(_ buf: RustBuffer) throws -> LnurlError { - return try FfiConverterTypeLnurlError.lift(buf) +public func FfiConverterTypeOnchainError_lift(_ buf: RustBuffer) throws -> OnchainError { + return try FfiConverterTypeOnchainError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeLnurlError_lower(_ value: LnurlError) -> RustBuffer { - return FfiConverterTypeLnurlError.lower(value) +public func FfiConverterTypeOnchainError_lower(_ value: OnchainError) -> RustBuffer { + return FfiConverterTypeOnchainError.lower(value) } -extension LnurlError: Equatable, Hashable {} +extension OnchainError: Equatable, Hashable {} -extension LnurlError: Codable {} +extension OnchainError: Codable {} -extension LnurlError: Foundation.LocalizedError { +extension OnchainError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -21073,58 +22430,73 @@ extension LnurlError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum ManualRefundStateEnum { +public enum PassphraseResponse { - case created - case approved - case rejected - case sent + /** + * User cancelled — aborts the pending operation. + */ + case cancel + /** + * Standard wallet — no passphrase, equivalent to `Some("")` on the device. + */ + case standard + /** + * Hidden wallet — derived from the passphrase entered on the host. + */ + case hidden(value: String + ) + /** + * Enter the passphrase on the Trezor device itself instead of on the host. + */ + case onDevice } #if compiler(>=6) -extension ManualRefundStateEnum: Sendable {} +extension PassphraseResponse: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeManualRefundStateEnum: FfiConverterRustBuffer { - typealias SwiftType = ManualRefundStateEnum +public struct FfiConverterTypePassphraseResponse: FfiConverterRustBuffer { + typealias SwiftType = PassphraseResponse - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ManualRefundStateEnum { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PassphraseResponse { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .created + case 1: return .cancel - case 2: return .approved + case 2: return .standard - case 3: return .rejected + case 3: return .hidden(value: try FfiConverterString.read(from: &buf) + ) - case 4: return .sent + case 4: return .onDevice default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: ManualRefundStateEnum, into buf: inout [UInt8]) { + public static func write(_ value: PassphraseResponse, into buf: inout [UInt8]) { switch value { - case .created: + case .cancel: writeInt(&buf, Int32(1)) - case .approved: + case .standard: writeInt(&buf, Int32(2)) - case .rejected: + case let .hidden(value): writeInt(&buf, Int32(3)) + FfiConverterString.write(value, into: &buf) + - - case .sent: + case .onDevice: writeInt(&buf, Int32(4)) } @@ -21135,21 +22507,21 @@ public struct FfiConverterTypeManualRefundStateEnum: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeManualRefundStateEnum_lift(_ buf: RustBuffer) throws -> ManualRefundStateEnum { - return try FfiConverterTypeManualRefundStateEnum.lift(buf) +public func FfiConverterTypePassphraseResponse_lift(_ buf: RustBuffer) throws -> PassphraseResponse { + return try FfiConverterTypePassphraseResponse.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeManualRefundStateEnum_lower(_ value: ManualRefundStateEnum) -> RustBuffer { - return FfiConverterTypeManualRefundStateEnum.lower(value) +public func FfiConverterTypePassphraseResponse_lower(_ value: PassphraseResponse) -> RustBuffer { + return FfiConverterTypePassphraseResponse.lower(value) } -extension ManualRefundStateEnum: Equatable, Hashable {} +extension PassphraseResponse: Equatable, Hashable {} -extension ManualRefundStateEnum: Codable {} +extension PassphraseResponse: Codable {} @@ -21159,82 +22531,53 @@ extension ManualRefundStateEnum: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum Network { +public enum PaymentState { - /** - * Mainnet Bitcoin. - */ - case bitcoin - /** - * Bitcoin's testnet network. - */ - case testnet - /** - * Bitcoin's testnet4 network. - */ - case testnet4 - /** - * Bitcoin's signet network. - */ - case signet - /** - * Bitcoin's regtest network. - */ - case regtest + case pending + case succeeded + case failed } #if compiler(>=6) -extension Network: Sendable {} +extension PaymentState: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeNetwork: FfiConverterRustBuffer { - typealias SwiftType = Network +public struct FfiConverterTypePaymentState: FfiConverterRustBuffer { + typealias SwiftType = PaymentState - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Network { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PaymentState { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .bitcoin - - case 2: return .testnet - - case 3: return .testnet4 + case 1: return .pending - case 4: return .signet + case 2: return .succeeded - case 5: return .regtest + case 3: return .failed default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: Network, into buf: inout [UInt8]) { + public static func write(_ value: PaymentState, into buf: inout [UInt8]) { switch value { - case .bitcoin: + case .pending: writeInt(&buf, Int32(1)) - case .testnet: + case .succeeded: writeInt(&buf, Int32(2)) - case .testnet4: + case .failed: writeInt(&buf, Int32(3)) - - case .signet: - writeInt(&buf, Int32(4)) - - - case .regtest: - writeInt(&buf, Int32(5)) - } } } @@ -21243,21 +22586,21 @@ public struct FfiConverterTypeNetwork: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeNetwork_lift(_ buf: RustBuffer) throws -> Network { - return try FfiConverterTypeNetwork.lift(buf) +public func FfiConverterTypePaymentState_lift(_ buf: RustBuffer) throws -> PaymentState { + return try FfiConverterTypePaymentState.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeNetwork_lower(_ value: Network) -> RustBuffer { - return FfiConverterTypeNetwork.lower(value) +public func FfiConverterTypePaymentState_lower(_ value: PaymentState) -> RustBuffer { + return FfiConverterTypePaymentState.lower(value) } -extension Network: Equatable, Hashable {} +extension PaymentState: Equatable, Hashable {} -extension Network: Codable {} +extension PaymentState: Codable {} @@ -21267,60 +22610,46 @@ extension Network: Codable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum NetworkType { +public enum PaymentType { - case bitcoin - case testnet - case regtest - case signet + case sent + case received } #if compiler(>=6) -extension NetworkType: Sendable {} +extension PaymentType: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeNetworkType: FfiConverterRustBuffer { - typealias SwiftType = NetworkType +public struct FfiConverterTypePaymentType: FfiConverterRustBuffer { + typealias SwiftType = PaymentType - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> NetworkType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PaymentType { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .bitcoin - - case 2: return .testnet - - case 3: return .regtest + case 1: return .sent - case 4: return .signet + case 2: return .received default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: NetworkType, into buf: inout [UInt8]) { + public static func write(_ value: PaymentType, into buf: inout [UInt8]) { switch value { - case .bitcoin: + case .sent: writeInt(&buf, Int32(1)) - case .testnet: + case .received: writeInt(&buf, Int32(2)) - - case .regtest: - writeInt(&buf, Int32(3)) - - - case .signet: - writeInt(&buf, Int32(4)) - } } } @@ -21329,21 +22658,21 @@ public struct FfiConverterTypeNetworkType: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeNetworkType_lift(_ buf: RustBuffer) throws -> NetworkType { - return try FfiConverterTypeNetworkType.lift(buf) +public func FfiConverterTypePaymentType_lift(_ buf: RustBuffer) throws -> PaymentType { + return try FfiConverterTypePaymentType.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeNetworkType_lower(_ value: NetworkType) -> RustBuffer { - return FfiConverterTypeNetworkType.lower(value) +public func FfiConverterTypePaymentType_lower(_ value: PaymentType) -> RustBuffer { + return FfiConverterTypePaymentType.lower(value) } -extension NetworkType: Equatable, Hashable {} +extension PaymentType: Equatable, Hashable {} -extension NetworkType: Codable {} +extension PaymentType: Codable {} @@ -21351,11 +22680,19 @@ extension NetworkType: Codable {} -public enum OnchainError: Swift.Error { +public enum PsbtCompletionError: Swift.Error { - case InvalidExtendedPublicKey(errorDetails: String + case InvalidPsbt(reason: String + ) + case CombineFailed(reason: String + ) + case FinalizationFailed(reason: String + ) + case VerificationFailed(reason: String + ) + case ExtractionFailed(reason: String ) } @@ -21363,34 +22700,66 @@ public enum OnchainError: Swift.Error { #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeOnchainError: FfiConverterRustBuffer { - typealias SwiftType = OnchainError +public struct FfiConverterTypePsbtCompletionError: FfiConverterRustBuffer { + typealias SwiftType = PsbtCompletionError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> OnchainError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PsbtCompletionError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .InvalidExtendedPublicKey( - errorDetails: try FfiConverterString.read(from: &buf) + case 1: return .InvalidPsbt( + reason: try FfiConverterString.read(from: &buf) + ) + case 2: return .CombineFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 3: return .FinalizationFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 4: return .VerificationFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 5: return .ExtractionFailed( + reason: try FfiConverterString.read(from: &buf) ) default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: OnchainError, into buf: inout [UInt8]) { + public static func write(_ value: PsbtCompletionError, into buf: inout [UInt8]) { switch value { - case let .InvalidExtendedPublicKey(errorDetails): + case let .InvalidPsbt(reason): writeInt(&buf, Int32(1)) - FfiConverterString.write(errorDetails, into: &buf) + FfiConverterString.write(reason, into: &buf) + + + case let .CombineFailed(reason): + writeInt(&buf, Int32(2)) + FfiConverterString.write(reason, into: &buf) + + + case let .FinalizationFailed(reason): + writeInt(&buf, Int32(3)) + FfiConverterString.write(reason, into: &buf) + + + case let .VerificationFailed(reason): + writeInt(&buf, Int32(4)) + FfiConverterString.write(reason, into: &buf) + + + case let .ExtractionFailed(reason): + writeInt(&buf, Int32(5)) + FfiConverterString.write(reason, into: &buf) } } @@ -21400,26 +22769,26 @@ public struct FfiConverterTypeOnchainError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeOnchainError_lift(_ buf: RustBuffer) throws -> OnchainError { - return try FfiConverterTypeOnchainError.lift(buf) +public func FfiConverterTypePsbtCompletionError_lift(_ buf: RustBuffer) throws -> PsbtCompletionError { + return try FfiConverterTypePsbtCompletionError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeOnchainError_lower(_ value: OnchainError) -> RustBuffer { - return FfiConverterTypeOnchainError.lower(value) +public func FfiConverterTypePsbtCompletionError_lower(_ value: PsbtCompletionError) -> RustBuffer { + return FfiConverterTypePsbtCompletionError.lower(value) } -extension OnchainError: Equatable, Hashable {} +extension PsbtCompletionError: Equatable, Hashable {} -extension OnchainError: Codable {} +extension PsbtCompletionError: Codable {} -extension OnchainError: Foundation.LocalizedError { +extension PsbtCompletionError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -21430,76 +22799,50 @@ extension OnchainError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * The type of a `pubkyauth://` deep-link flow. + */ -public enum PassphraseResponse { +public enum PubkyAuthKind { - /** - * User cancelled — aborts the pending operation. - */ - case cancel - /** - * Standard wallet — no passphrase, equivalent to `Some("")` on the device. - */ - case standard - /** - * Hidden wallet — derived from the passphrase entered on the host. - */ - case hidden(value: String - ) - /** - * Enter the passphrase on the Trezor device itself instead of on the host. - */ - case onDevice + case signin + case signup } #if compiler(>=6) -extension PassphraseResponse: Sendable {} +extension PubkyAuthKind: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypePassphraseResponse: FfiConverterRustBuffer { - typealias SwiftType = PassphraseResponse +public struct FfiConverterTypePubkyAuthKind: FfiConverterRustBuffer { + typealias SwiftType = PubkyAuthKind - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PassphraseResponse { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PubkyAuthKind { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .cancel - - case 2: return .standard - - case 3: return .hidden(value: try FfiConverterString.read(from: &buf) - ) + case 1: return .signin - case 4: return .onDevice + case 2: return .signup default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: PassphraseResponse, into buf: inout [UInt8]) { + public static func write(_ value: PubkyAuthKind, into buf: inout [UInt8]) { switch value { - case .cancel: + case .signin: writeInt(&buf, Int32(1)) - case .standard: + case .signup: writeInt(&buf, Int32(2)) - - case let .hidden(value): - writeInt(&buf, Int32(3)) - FfiConverterString.write(value, into: &buf) - - - case .onDevice: - writeInt(&buf, Int32(4)) - } } } @@ -21508,77 +22851,141 @@ public struct FfiConverterTypePassphraseResponse: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePassphraseResponse_lift(_ buf: RustBuffer) throws -> PassphraseResponse { - return try FfiConverterTypePassphraseResponse.lift(buf) +public func FfiConverterTypePubkyAuthKind_lift(_ buf: RustBuffer) throws -> PubkyAuthKind { + return try FfiConverterTypePubkyAuthKind.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePassphraseResponse_lower(_ value: PassphraseResponse) -> RustBuffer { - return FfiConverterTypePassphraseResponse.lower(value) +public func FfiConverterTypePubkyAuthKind_lower(_ value: PubkyAuthKind) -> RustBuffer { + return FfiConverterTypePubkyAuthKind.lower(value) } -extension PassphraseResponse: Equatable, Hashable {} +extension PubkyAuthKind: Equatable, Hashable {} -extension PassphraseResponse: Codable {} +extension PubkyAuthKind: Codable {} -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum PaymentState { +public enum PubkyError: Swift.Error { + - case pending - case succeeded - case failed + + case InvalidCapabilities(reason: String + ) + case AuthFailed(reason: String + ) + case NoActiveFlow + case ResolutionFailed(reason: String + ) + case FetchFailed(reason: String + ) + case ProfileNotFound + case ProfileParseFailed(reason: String + ) + case KeyError(reason: String + ) + case WriteFailed(reason: String + ) } -#if compiler(>=6) -extension PaymentState: Sendable {} -#endif - #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypePaymentState: FfiConverterRustBuffer { - typealias SwiftType = PaymentState +public struct FfiConverterTypePubkyError: FfiConverterRustBuffer { + typealias SwiftType = PubkyError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PaymentState { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PubkyError { let variant: Int32 = try readInt(&buf) switch variant { + - case 1: return .pending + - case 2: return .succeeded + case 1: return .InvalidCapabilities( + reason: try FfiConverterString.read(from: &buf) + ) + case 2: return .AuthFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 3: return .NoActiveFlow + case 4: return .ResolutionFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 5: return .FetchFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 6: return .ProfileNotFound + case 7: return .ProfileParseFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 8: return .KeyError( + reason: try FfiConverterString.read(from: &buf) + ) + case 9: return .WriteFailed( + reason: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: PubkyError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .InvalidCapabilities(reason): + writeInt(&buf, Int32(1)) + FfiConverterString.write(reason, into: &buf) + + + case let .AuthFailed(reason): + writeInt(&buf, Int32(2)) + FfiConverterString.write(reason, into: &buf) + - case 3: return .failed + case .NoActiveFlow: + writeInt(&buf, Int32(3)) - default: throw UniffiInternalError.unexpectedEnumCase - } - } - - public static func write(_ value: PaymentState, into buf: inout [UInt8]) { - switch value { + case let .ResolutionFailed(reason): + writeInt(&buf, Int32(4)) + FfiConverterString.write(reason, into: &buf) + - case .pending: - writeInt(&buf, Int32(1)) + case let .FetchFailed(reason): + writeInt(&buf, Int32(5)) + FfiConverterString.write(reason, into: &buf) + + case .ProfileNotFound: + writeInt(&buf, Int32(6)) - case .succeeded: - writeInt(&buf, Int32(2)) + case let .ProfileParseFailed(reason): + writeInt(&buf, Int32(7)) + FfiConverterString.write(reason, into: &buf) + - case .failed: - writeInt(&buf, Int32(3)) + case let .KeyError(reason): + writeInt(&buf, Int32(8)) + FfiConverterString.write(reason, into: &buf) + + case let .WriteFailed(reason): + writeInt(&buf, Int32(9)) + FfiConverterString.write(reason, into: &buf) + } } } @@ -21587,70 +22994,165 @@ public struct FfiConverterTypePaymentState: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePaymentState_lift(_ buf: RustBuffer) throws -> PaymentState { - return try FfiConverterTypePaymentState.lift(buf) +public func FfiConverterTypePubkyError_lift(_ buf: RustBuffer) throws -> PubkyError { + return try FfiConverterTypePubkyError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePaymentState_lower(_ value: PaymentState) -> RustBuffer { - return FfiConverterTypePaymentState.lower(value) +public func FfiConverterTypePubkyError_lower(_ value: PubkyError) -> RustBuffer { + return FfiConverterTypePubkyError.lower(value) } -extension PaymentState: Equatable, Hashable {} +extension PubkyError: Equatable, Hashable {} + +extension PubkyError: Codable {} -extension PaymentState: Codable {} +extension PubkyError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -public enum PaymentType { +public enum Scanner { - case sent - case received + case onChain(invoice: OnChainInvoice + ) + case lightning(invoice: LightningInvoice + ) + case pubkyAuth(data: String + ) + case lnurlChannel(data: LnurlChannelData + ) + case lnurlAuth(data: LnurlAuthData + ) + case lnurlWithdraw(data: LnurlWithdrawData + ) + case lnurlAddress(data: LnurlAddressData + ) + case lnurlPay(data: LnurlPayData + ) + case nodeId(url: String, network: NetworkType + ) + case gift(code: String, amount: UInt64 + ) } #if compiler(>=6) -extension PaymentType: Sendable {} +extension Scanner: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypePaymentType: FfiConverterRustBuffer { - typealias SwiftType = PaymentType +public struct FfiConverterTypeScanner: FfiConverterRustBuffer { + typealias SwiftType = Scanner - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PaymentType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Scanner { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .sent + case 1: return .onChain(invoice: try FfiConverterTypeOnChainInvoice.read(from: &buf) + ) - case 2: return .received + case 2: return .lightning(invoice: try FfiConverterTypeLightningInvoice.read(from: &buf) + ) + + case 3: return .pubkyAuth(data: try FfiConverterString.read(from: &buf) + ) + + case 4: return .lnurlChannel(data: try FfiConverterTypeLnurlChannelData.read(from: &buf) + ) + + case 5: return .lnurlAuth(data: try FfiConverterTypeLnurlAuthData.read(from: &buf) + ) + + case 6: return .lnurlWithdraw(data: try FfiConverterTypeLnurlWithdrawData.read(from: &buf) + ) + + case 7: return .lnurlAddress(data: try FfiConverterTypeLnurlAddressData.read(from: &buf) + ) + + case 8: return .lnurlPay(data: try FfiConverterTypeLnurlPayData.read(from: &buf) + ) + + case 9: return .nodeId(url: try FfiConverterString.read(from: &buf), network: try FfiConverterTypeNetworkType.read(from: &buf) + ) + + case 10: return .gift(code: try FfiConverterString.read(from: &buf), amount: try FfiConverterUInt64.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: PaymentType, into buf: inout [UInt8]) { + public static func write(_ value: Scanner, into buf: inout [UInt8]) { switch value { - case .sent: + case let .onChain(invoice): writeInt(&buf, Int32(1)) + FfiConverterTypeOnChainInvoice.write(invoice, into: &buf) + - - case .received: + case let .lightning(invoice): writeInt(&buf, Int32(2)) + FfiConverterTypeLightningInvoice.write(invoice, into: &buf) + + + case let .pubkyAuth(data): + writeInt(&buf, Int32(3)) + FfiConverterString.write(data, into: &buf) + + + case let .lnurlChannel(data): + writeInt(&buf, Int32(4)) + FfiConverterTypeLnurlChannelData.write(data, into: &buf) + + + case let .lnurlAuth(data): + writeInt(&buf, Int32(5)) + FfiConverterTypeLnurlAuthData.write(data, into: &buf) + + + case let .lnurlWithdraw(data): + writeInt(&buf, Int32(6)) + FfiConverterTypeLnurlWithdrawData.write(data, into: &buf) + + + case let .lnurlAddress(data): + writeInt(&buf, Int32(7)) + FfiConverterTypeLnurlAddressData.write(data, into: &buf) + + + case let .lnurlPay(data): + writeInt(&buf, Int32(8)) + FfiConverterTypeLnurlPayData.write(data, into: &buf) + + + case let .nodeId(url,network): + writeInt(&buf, Int32(9)) + FfiConverterString.write(url, into: &buf) + FfiConverterTypeNetworkType.write(network, into: &buf) + + case let .gift(code,amount): + writeInt(&buf, Int32(10)) + FfiConverterString.write(code, into: &buf) + FfiConverterUInt64.write(amount, into: &buf) + } } } @@ -21659,21 +23161,21 @@ public struct FfiConverterTypePaymentType: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePaymentType_lift(_ buf: RustBuffer) throws -> PaymentType { - return try FfiConverterTypePaymentType.lift(buf) +public func FfiConverterTypeScanner_lift(_ buf: RustBuffer) throws -> Scanner { + return try FfiConverterTypeScanner.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePaymentType_lower(_ value: PaymentType) -> RustBuffer { - return FfiConverterTypePaymentType.lower(value) +public func FfiConverterTypeScanner_lower(_ value: Scanner) -> RustBuffer { + return FfiConverterTypeScanner.lower(value) } -extension PaymentType: Equatable, Hashable {} +extension Scanner: Equatable, Hashable {} -extension PaymentType: Codable {} +extension Scanner: Codable {} @@ -21681,87 +23183,55 @@ extension PaymentType: Codable {} -public enum PsbtCompletionError: Swift.Error { +public enum SeedQrError: Swift.Error { - case InvalidPsbt(reason: String - ) - case CombineFailed(reason: String - ) - case FinalizationFailed(reason: String - ) - case VerificationFailed(reason: String - ) - case ExtractionFailed(reason: String - ) + case InvalidStandardPayload + case InvalidCompactPayload + case InvalidMnemonic } #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypePsbtCompletionError: FfiConverterRustBuffer { - typealias SwiftType = PsbtCompletionError +public struct FfiConverterTypeSeedQrError: FfiConverterRustBuffer { + typealias SwiftType = SeedQrError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PsbtCompletionError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SeedQrError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .InvalidPsbt( - reason: try FfiConverterString.read(from: &buf) - ) - case 2: return .CombineFailed( - reason: try FfiConverterString.read(from: &buf) - ) - case 3: return .FinalizationFailed( - reason: try FfiConverterString.read(from: &buf) - ) - case 4: return .VerificationFailed( - reason: try FfiConverterString.read(from: &buf) - ) - case 5: return .ExtractionFailed( - reason: try FfiConverterString.read(from: &buf) - ) + case 1: return .InvalidStandardPayload + case 2: return .InvalidCompactPayload + case 3: return .InvalidMnemonic default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: PsbtCompletionError, into buf: inout [UInt8]) { + public static func write(_ value: SeedQrError, into buf: inout [UInt8]) { switch value { - case let .InvalidPsbt(reason): + case .InvalidStandardPayload: writeInt(&buf, Int32(1)) - FfiConverterString.write(reason, into: &buf) - - case let .CombineFailed(reason): + + case .InvalidCompactPayload: writeInt(&buf, Int32(2)) - FfiConverterString.write(reason, into: &buf) - - case let .FinalizationFailed(reason): - writeInt(&buf, Int32(3)) - FfiConverterString.write(reason, into: &buf) - - case let .VerificationFailed(reason): - writeInt(&buf, Int32(4)) - FfiConverterString.write(reason, into: &buf) - + case .InvalidMnemonic: + writeInt(&buf, Int32(3)) - case let .ExtractionFailed(reason): - writeInt(&buf, Int32(5)) - FfiConverterString.write(reason, into: &buf) - } } } @@ -21770,26 +23240,26 @@ public struct FfiConverterTypePsbtCompletionError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePsbtCompletionError_lift(_ buf: RustBuffer) throws -> PsbtCompletionError { - return try FfiConverterTypePsbtCompletionError.lift(buf) +public func FfiConverterTypeSeedQrError_lift(_ buf: RustBuffer) throws -> SeedQrError { + return try FfiConverterTypeSeedQrError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePsbtCompletionError_lower(_ value: PsbtCompletionError) -> RustBuffer { - return FfiConverterTypePsbtCompletionError.lower(value) +public func FfiConverterTypeSeedQrError_lower(_ value: SeedQrError) -> RustBuffer { + return FfiConverterTypeSeedQrError.lower(value) } -extension PsbtCompletionError: Equatable, Hashable {} +extension SeedQrError: Equatable, Hashable {} -extension PsbtCompletionError: Codable {} +extension SeedQrError: Codable {} -extension PsbtCompletionError: Foundation.LocalizedError { +extension SeedQrError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -21800,48 +23270,45 @@ extension PsbtCompletionError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -/** - * The type of a `pubkyauth://` deep-link flow. - */ -public enum PubkyAuthKind { +public enum SortDirection { - case signin - case signup + case asc + case desc } #if compiler(>=6) -extension PubkyAuthKind: Sendable {} +extension SortDirection: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypePubkyAuthKind: FfiConverterRustBuffer { - typealias SwiftType = PubkyAuthKind +public struct FfiConverterTypeSortDirection: FfiConverterRustBuffer { + typealias SwiftType = SortDirection - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PubkyAuthKind { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SortDirection { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .signin + case 1: return .asc - case 2: return .signup + case 2: return .desc default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: PubkyAuthKind, into buf: inout [UInt8]) { + public static func write(_ value: SortDirection, into buf: inout [UInt8]) { switch value { - case .signin: + case .asc: writeInt(&buf, Int32(1)) - case .signup: + case .desc: writeInt(&buf, Int32(2)) } @@ -21852,21 +23319,21 @@ public struct FfiConverterTypePubkyAuthKind: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePubkyAuthKind_lift(_ buf: RustBuffer) throws -> PubkyAuthKind { - return try FfiConverterTypePubkyAuthKind.lift(buf) +public func FfiConverterTypeSortDirection_lift(_ buf: RustBuffer) throws -> SortDirection { + return try FfiConverterTypeSortDirection.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePubkyAuthKind_lower(_ value: PubkyAuthKind) -> RustBuffer { - return FfiConverterTypePubkyAuthKind.lower(value) +public func FfiConverterTypeSortDirection_lower(_ value: SortDirection) -> RustBuffer { + return FfiConverterTypeSortDirection.lower(value) } -extension PubkyAuthKind: Equatable, Hashable {} +extension SortDirection: Equatable, Hashable {} -extension PubkyAuthKind: Codable {} +extension SortDirection: Codable {} @@ -21874,119 +23341,59 @@ extension PubkyAuthKind: Codable {} -public enum PubkyError: Swift.Error { +public enum SweepError: Swift.Error { - case InvalidCapabilities(reason: String - ) - case AuthFailed(reason: String - ) - case NoActiveFlow - case ResolutionFailed(reason: String - ) - case FetchFailed(reason: String - ) - case ProfileNotFound - case ProfileParseFailed(reason: String - ) - case KeyError(reason: String - ) - case WriteFailed(reason: String + case SweepFailed(String ) + case NoUtxosFound + case InvalidMnemonic } #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypePubkyError: FfiConverterRustBuffer { - typealias SwiftType = PubkyError +public struct FfiConverterTypeSweepError: FfiConverterRustBuffer { + typealias SwiftType = SweepError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PubkyError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SweepError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .InvalidCapabilities( - reason: try FfiConverterString.read(from: &buf) - ) - case 2: return .AuthFailed( - reason: try FfiConverterString.read(from: &buf) - ) - case 3: return .NoActiveFlow - case 4: return .ResolutionFailed( - reason: try FfiConverterString.read(from: &buf) - ) - case 5: return .FetchFailed( - reason: try FfiConverterString.read(from: &buf) - ) - case 6: return .ProfileNotFound - case 7: return .ProfileParseFailed( - reason: try FfiConverterString.read(from: &buf) - ) - case 8: return .KeyError( - reason: try FfiConverterString.read(from: &buf) - ) - case 9: return .WriteFailed( - reason: try FfiConverterString.read(from: &buf) + case 1: return .SweepFailed( + try FfiConverterString.read(from: &buf) ) + case 2: return .NoUtxosFound + case 3: return .InvalidMnemonic default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: PubkyError, into buf: inout [UInt8]) { + public static func write(_ value: SweepError, into buf: inout [UInt8]) { switch value { - case let .InvalidCapabilities(reason): + case let .SweepFailed(v1): writeInt(&buf, Int32(1)) - FfiConverterString.write(reason, into: &buf) + FfiConverterString.write(v1, into: &buf) - case let .AuthFailed(reason): + case .NoUtxosFound: writeInt(&buf, Int32(2)) - FfiConverterString.write(reason, into: &buf) - - - case .NoActiveFlow: - writeInt(&buf, Int32(3)) - case let .ResolutionFailed(reason): - writeInt(&buf, Int32(4)) - FfiConverterString.write(reason, into: &buf) - - - case let .FetchFailed(reason): - writeInt(&buf, Int32(5)) - FfiConverterString.write(reason, into: &buf) - - - case .ProfileNotFound: - writeInt(&buf, Int32(6)) - - - case let .ProfileParseFailed(reason): - writeInt(&buf, Int32(7)) - FfiConverterString.write(reason, into: &buf) - - - case let .KeyError(reason): - writeInt(&buf, Int32(8)) - FfiConverterString.write(reason, into: &buf) - + case .InvalidMnemonic: + writeInt(&buf, Int32(3)) - case let .WriteFailed(reason): - writeInt(&buf, Int32(9)) - FfiConverterString.write(reason, into: &buf) - } } } @@ -21995,26 +23402,26 @@ public struct FfiConverterTypePubkyError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePubkyError_lift(_ buf: RustBuffer) throws -> PubkyError { - return try FfiConverterTypePubkyError.lift(buf) +public func FfiConverterTypeSweepError_lift(_ buf: RustBuffer) throws -> SweepError { + return try FfiConverterTypeSweepError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypePubkyError_lower(_ value: PubkyError) -> RustBuffer { - return FfiConverterTypePubkyError.lower(value) +public func FfiConverterTypeSweepError_lower(_ value: SweepError) -> RustBuffer { + return FfiConverterTypeSweepError.lower(value) } -extension PubkyError: Equatable, Hashable {} +extension SweepError: Equatable, Hashable {} -extension PubkyError: Codable {} +extension SweepError: Codable {} -extension PubkyError: Foundation.LocalizedError { +extension SweepError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -22025,135 +23432,76 @@ extension PubkyError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Bitcoin network / coin type for Trezor operations. + */ -public enum Scanner { +public enum TrezorCoinType { - case onChain(invoice: OnChainInvoice - ) - case lightning(invoice: LightningInvoice - ) - case pubkyAuth(data: String - ) - case lnurlChannel(data: LnurlChannelData - ) - case lnurlAuth(data: LnurlAuthData - ) - case lnurlWithdraw(data: LnurlWithdrawData - ) - case lnurlAddress(data: LnurlAddressData - ) - case lnurlPay(data: LnurlPayData - ) - case nodeId(url: String, network: NetworkType - ) - case gift(code: String, amount: UInt64 - ) + /** + * Bitcoin mainnet + */ + case bitcoin + /** + * Bitcoin testnet + */ + case testnet + /** + * Bitcoin signet (treated as testnet by the device) + */ + case signet + /** + * Bitcoin regtest + */ + case regtest } #if compiler(>=6) -extension Scanner: Sendable {} +extension TrezorCoinType: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeScanner: FfiConverterRustBuffer { - typealias SwiftType = Scanner +public struct FfiConverterTypeTrezorCoinType: FfiConverterRustBuffer { + typealias SwiftType = TrezorCoinType - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Scanner { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorCoinType { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .onChain(invoice: try FfiConverterTypeOnChainInvoice.read(from: &buf) - ) - - case 2: return .lightning(invoice: try FfiConverterTypeLightningInvoice.read(from: &buf) - ) - - case 3: return .pubkyAuth(data: try FfiConverterString.read(from: &buf) - ) - - case 4: return .lnurlChannel(data: try FfiConverterTypeLnurlChannelData.read(from: &buf) - ) - - case 5: return .lnurlAuth(data: try FfiConverterTypeLnurlAuthData.read(from: &buf) - ) - - case 6: return .lnurlWithdraw(data: try FfiConverterTypeLnurlWithdrawData.read(from: &buf) - ) - - case 7: return .lnurlAddress(data: try FfiConverterTypeLnurlAddressData.read(from: &buf) - ) + case 1: return .bitcoin - case 8: return .lnurlPay(data: try FfiConverterTypeLnurlPayData.read(from: &buf) - ) + case 2: return .testnet - case 9: return .nodeId(url: try FfiConverterString.read(from: &buf), network: try FfiConverterTypeNetworkType.read(from: &buf) - ) + case 3: return .signet - case 10: return .gift(code: try FfiConverterString.read(from: &buf), amount: try FfiConverterUInt64.read(from: &buf) - ) + case 4: return .regtest default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: Scanner, into buf: inout [UInt8]) { + public static func write(_ value: TrezorCoinType, into buf: inout [UInt8]) { switch value { - case let .onChain(invoice): - writeInt(&buf, Int32(1)) - FfiConverterTypeOnChainInvoice.write(invoice, into: &buf) - - - case let .lightning(invoice): - writeInt(&buf, Int32(2)) - FfiConverterTypeLightningInvoice.write(invoice, into: &buf) - - - case let .pubkyAuth(data): - writeInt(&buf, Int32(3)) - FfiConverterString.write(data, into: &buf) - - - case let .lnurlChannel(data): - writeInt(&buf, Int32(4)) - FfiConverterTypeLnurlChannelData.write(data, into: &buf) - + case .bitcoin: + writeInt(&buf, Int32(1)) - case let .lnurlAuth(data): - writeInt(&buf, Int32(5)) - FfiConverterTypeLnurlAuthData.write(data, into: &buf) - - case let .lnurlWithdraw(data): - writeInt(&buf, Int32(6)) - FfiConverterTypeLnurlWithdrawData.write(data, into: &buf) - + case .testnet: + writeInt(&buf, Int32(2)) - case let .lnurlAddress(data): - writeInt(&buf, Int32(7)) - FfiConverterTypeLnurlAddressData.write(data, into: &buf) - - case let .lnurlPay(data): - writeInt(&buf, Int32(8)) - FfiConverterTypeLnurlPayData.write(data, into: &buf) - + case .signet: + writeInt(&buf, Int32(3)) - case let .nodeId(url,network): - writeInt(&buf, Int32(9)) - FfiConverterString.write(url, into: &buf) - FfiConverterTypeNetworkType.write(network, into: &buf) - - case let .gift(code,amount): - writeInt(&buf, Int32(10)) - FfiConverterString.write(code, into: &buf) - FfiConverterUInt64.write(amount, into: &buf) - + case .regtest: + writeInt(&buf, Int32(4)) + } } } @@ -22162,21 +23510,21 @@ public struct FfiConverterTypeScanner: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeScanner_lift(_ buf: RustBuffer) throws -> Scanner { - return try FfiConverterTypeScanner.lift(buf) +public func FfiConverterTypeTrezorCoinType_lift(_ buf: RustBuffer) throws -> TrezorCoinType { + return try FfiConverterTypeTrezorCoinType.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeScanner_lower(_ value: Scanner) -> RustBuffer { - return FfiConverterTypeScanner.lower(value) +public func FfiConverterTypeTrezorCoinType_lower(_ value: TrezorCoinType) -> RustBuffer { + return FfiConverterTypeTrezorCoinType.lower(value) } -extension Scanner: Equatable, Hashable {} +extension TrezorCoinType: Equatable, Hashable {} -extension Scanner: Codable {} +extension TrezorCoinType: Codable {} @@ -22184,55 +23532,261 @@ extension Scanner: Codable {} -public enum SeedQrError: Swift.Error { +/** + * Trezor-related errors exposed via FFI. + */ +public enum TrezorError: Swift.Error { - case InvalidStandardPayload - case InvalidCompactPayload - case InvalidMnemonic + /** + * Transport layer error (USB/Bluetooth communication) + */ + case TransportError(errorDetails: String + ) + /** + * No Trezor device found + */ + case DeviceNotFound + /** + * Device disconnected during operation + */ + case DeviceDisconnected + /** + * Device is busy and the caller should back off before retrying + */ + case DeviceBusy + /** + * Connection error + */ + case ConnectionError(errorDetails: String + ) + /** + * Protocol error (encoding/decoding) + */ + case ProtocolError(errorDetails: String + ) + /** + * Pairing required for Bluetooth connection + */ + case PairingRequired + /** + * Pairing failed + */ + case PairingFailed(errorDetails: String + ) + /** + * PIN is required + */ + case PinRequired + /** + * PIN entry cancelled + */ + case PinCancelled + /** + * Invalid PIN entered + */ + case InvalidPin + /** + * Passphrase is required + */ + case PassphraseRequired + /** + * Passphrase entry cancelled + */ + case PassphraseCancelled + /** + * Action cancelled by user on device + */ + case UserCancelled + /** + * Operation timed out + */ + case Timeout + /** + * Invalid derivation path + */ + case InvalidPath(errorDetails: String + ) + /** + * Device returned an error + */ + case DeviceError(errorDetails: String + ) + /** + * Trezor manager not initialized + */ + case NotInitialized + /** + * No device connected + */ + case NotConnected + /** + * Session error + */ + case SessionError(errorDetails: String + ) + /** + * IO error + */ + case IoError(errorDetails: String + ) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeSeedQrError: FfiConverterRustBuffer { - typealias SwiftType = SeedQrError +public struct FfiConverterTypeTrezorError: FfiConverterRustBuffer { + typealias SwiftType = TrezorError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SeedQrError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .InvalidStandardPayload - case 2: return .InvalidCompactPayload - case 3: return .InvalidMnemonic + case 1: return .TransportError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 2: return .DeviceNotFound + case 3: return .DeviceDisconnected + case 4: return .DeviceBusy + case 5: return .ConnectionError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 6: return .ProtocolError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 7: return .PairingRequired + case 8: return .PairingFailed( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 9: return .PinRequired + case 10: return .PinCancelled + case 11: return .InvalidPin + case 12: return .PassphraseRequired + case 13: return .PassphraseCancelled + case 14: return .UserCancelled + case 15: return .Timeout + case 16: return .InvalidPath( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 17: return .DeviceError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 18: return .NotInitialized + case 19: return .NotConnected + case 20: return .SessionError( + errorDetails: try FfiConverterString.read(from: &buf) + ) + case 21: return .IoError( + errorDetails: try FfiConverterString.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: SeedQrError, into buf: inout [UInt8]) { + public static func write(_ value: TrezorError, into buf: inout [UInt8]) { switch value { - + + + + case let .TransportError(errorDetails): + writeInt(&buf, Int32(1)) + FfiConverterString.write(errorDetails, into: &buf) + + + case .DeviceNotFound: + writeInt(&buf, Int32(2)) + + + case .DeviceDisconnected: + writeInt(&buf, Int32(3)) + + + case .DeviceBusy: + writeInt(&buf, Int32(4)) + + + case let .ConnectionError(errorDetails): + writeInt(&buf, Int32(5)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .ProtocolError(errorDetails): + writeInt(&buf, Int32(6)) + FfiConverterString.write(errorDetails, into: &buf) + + + case .PairingRequired: + writeInt(&buf, Int32(7)) + + + case let .PairingFailed(errorDetails): + writeInt(&buf, Int32(8)) + FfiConverterString.write(errorDetails, into: &buf) + + + case .PinRequired: + writeInt(&buf, Int32(9)) + + + case .PinCancelled: + writeInt(&buf, Int32(10)) + + + case .InvalidPin: + writeInt(&buf, Int32(11)) + + + case .PassphraseRequired: + writeInt(&buf, Int32(12)) + + + case .PassphraseCancelled: + writeInt(&buf, Int32(13)) + + + case .UserCancelled: + writeInt(&buf, Int32(14)) + + case .Timeout: + writeInt(&buf, Int32(15)) - case .InvalidStandardPayload: - writeInt(&buf, Int32(1)) + case let .InvalidPath(errorDetails): + writeInt(&buf, Int32(16)) + FfiConverterString.write(errorDetails, into: &buf) + + + case let .DeviceError(errorDetails): + writeInt(&buf, Int32(17)) + FfiConverterString.write(errorDetails, into: &buf) + - case .InvalidCompactPayload: - writeInt(&buf, Int32(2)) + case .NotInitialized: + writeInt(&buf, Int32(18)) - case .InvalidMnemonic: - writeInt(&buf, Int32(3)) + case .NotConnected: + writeInt(&buf, Int32(19)) + + + case let .SessionError(errorDetails): + writeInt(&buf, Int32(20)) + FfiConverterString.write(errorDetails, into: &buf) + + case let .IoError(errorDetails): + writeInt(&buf, Int32(21)) + FfiConverterString.write(errorDetails, into: &buf) + } } } @@ -22241,26 +23795,26 @@ public struct FfiConverterTypeSeedQrError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeSeedQrError_lift(_ buf: RustBuffer) throws -> SeedQrError { - return try FfiConverterTypeSeedQrError.lift(buf) +public func FfiConverterTypeTrezorError_lift(_ buf: RustBuffer) throws -> TrezorError { + return try FfiConverterTypeTrezorError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeSeedQrError_lower(_ value: SeedQrError) -> RustBuffer { - return FfiConverterTypeSeedQrError.lower(value) +public func FfiConverterTypeTrezorError_lower(_ value: TrezorError) -> RustBuffer { + return FfiConverterTypeTrezorError.lower(value) } -extension SeedQrError: Equatable, Hashable {} +extension TrezorError: Equatable, Hashable {} -extension SeedQrError: Codable {} +extension TrezorError: Codable {} -extension SeedQrError: Foundation.LocalizedError { +extension TrezorError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -22271,129 +23825,95 @@ extension SeedQrError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Script types for address derivation. + */ -public enum SortDirection { +public enum TrezorScriptType { - case asc - case desc + /** + * P2PKH (legacy) + */ + case spendAddress + /** + * P2SH-P2WPKH (nested SegWit) + */ + case spendP2shWitness + /** + * P2WPKH (native SegWit) + */ + case spendWitness + /** + * P2TR (Taproot) + */ + case spendTaproot + /** + * P2SH multisig + */ + case spendMultisig + /** + * External/watch-only input (not signed by device) + */ + case external } #if compiler(>=6) -extension SortDirection: Sendable {} +extension TrezorScriptType: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeSortDirection: FfiConverterRustBuffer { - typealias SwiftType = SortDirection +public struct FfiConverterTypeTrezorScriptType: FfiConverterRustBuffer { + typealias SwiftType = TrezorScriptType - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SortDirection { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorScriptType { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .asc + case 1: return .spendAddress - case 2: return .desc + case 2: return .spendP2shWitness + + case 3: return .spendWitness + + case 4: return .spendTaproot + + case 5: return .spendMultisig + + case 6: return .external default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: SortDirection, into buf: inout [UInt8]) { + public static func write(_ value: TrezorScriptType, into buf: inout [UInt8]) { switch value { - case .asc: + case .spendAddress: writeInt(&buf, Int32(1)) - case .desc: + case .spendP2shWitness: writeInt(&buf, Int32(2)) - } - } -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeSortDirection_lift(_ buf: RustBuffer) throws -> SortDirection { - return try FfiConverterTypeSortDirection.lift(buf) -} - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeSortDirection_lower(_ value: SortDirection) -> RustBuffer { - return FfiConverterTypeSortDirection.lower(value) -} - - -extension SortDirection: Equatable, Hashable {} - -extension SortDirection: Codable {} - - - - - - - -public enum SweepError: Swift.Error { - - - - case SweepFailed(String - ) - case NoUtxosFound - case InvalidMnemonic -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeSweepError: FfiConverterRustBuffer { - typealias SwiftType = SweepError - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SweepError { - let variant: Int32 = try readInt(&buf) - switch variant { - - - - case 1: return .SweepFailed( - try FfiConverterString.read(from: &buf) - ) - case 2: return .NoUtxosFound - case 3: return .InvalidMnemonic - - default: throw UniffiInternalError.unexpectedEnumCase - } - } - - public static func write(_ value: SweepError, into buf: inout [UInt8]) { - switch value { - + case .spendWitness: + writeInt(&buf, Int32(3)) - + case .spendTaproot: + writeInt(&buf, Int32(4)) - case let .SweepFailed(v1): - writeInt(&buf, Int32(1)) - FfiConverterString.write(v1, into: &buf) - - case .NoUtxosFound: - writeInt(&buf, Int32(2)) + case .spendMultisig: + writeInt(&buf, Int32(5)) - case .InvalidMnemonic: - writeInt(&buf, Int32(3)) + case .external: + writeInt(&buf, Int32(6)) } } @@ -22403,30 +23923,23 @@ public struct FfiConverterTypeSweepError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeSweepError_lift(_ buf: RustBuffer) throws -> SweepError { - return try FfiConverterTypeSweepError.lift(buf) +public func FfiConverterTypeTrezorScriptType_lift(_ buf: RustBuffer) throws -> TrezorScriptType { + return try FfiConverterTypeTrezorScriptType.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeSweepError_lower(_ value: SweepError) -> RustBuffer { - return FfiConverterTypeSweepError.lower(value) +public func FfiConverterTypeTrezorScriptType_lower(_ value: TrezorScriptType) -> RustBuffer { + return FfiConverterTypeTrezorScriptType.lower(value) } -extension SweepError: Equatable, Hashable {} - -extension SweepError: Codable {} - +extension TrezorScriptType: Equatable, Hashable {} +extension TrezorScriptType: Codable {} -extension SweepError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} @@ -22434,74 +23947,44 @@ extension SweepError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Bitcoin network / coin type for Trezor operations. + * Structured transport error code returned by native callback operations. */ -public enum TrezorCoinType { +public enum TrezorTransportErrorCode { /** - * Bitcoin mainnet - */ - case bitcoin - /** - * Bitcoin testnet - */ - case testnet - /** - * Bitcoin signet (treated as testnet by the device) - */ - case signet - /** - * Bitcoin regtest + * Device is busy and the caller should back off before retrying. */ - case regtest + case deviceBusy } #if compiler(>=6) -extension TrezorCoinType: Sendable {} +extension TrezorTransportErrorCode: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeTrezorCoinType: FfiConverterRustBuffer { - typealias SwiftType = TrezorCoinType +public struct FfiConverterTypeTrezorTransportErrorCode: FfiConverterRustBuffer { + typealias SwiftType = TrezorTransportErrorCode - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorCoinType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorTransportErrorCode { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .bitcoin - - case 2: return .testnet - - case 3: return .signet - - case 4: return .regtest + case 1: return .deviceBusy default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: TrezorCoinType, into buf: inout [UInt8]) { - switch value { - - - case .bitcoin: - writeInt(&buf, Int32(1)) - - - case .testnet: - writeInt(&buf, Int32(2)) - - - case .signet: - writeInt(&buf, Int32(3)) + public static func write(_ value: TrezorTransportErrorCode, into buf: inout [UInt8]) { + switch value { - case .regtest: - writeInt(&buf, Int32(4)) + case .deviceBusy: + writeInt(&buf, Int32(1)) } } @@ -22511,283 +23994,79 @@ public struct FfiConverterTypeTrezorCoinType: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorCoinType_lift(_ buf: RustBuffer) throws -> TrezorCoinType { - return try FfiConverterTypeTrezorCoinType.lift(buf) +public func FfiConverterTypeTrezorTransportErrorCode_lift(_ buf: RustBuffer) throws -> TrezorTransportErrorCode { + return try FfiConverterTypeTrezorTransportErrorCode.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorCoinType_lower(_ value: TrezorCoinType) -> RustBuffer { - return FfiConverterTypeTrezorCoinType.lower(value) +public func FfiConverterTypeTrezorTransportErrorCode_lower(_ value: TrezorTransportErrorCode) -> RustBuffer { + return FfiConverterTypeTrezorTransportErrorCode.lower(value) } -extension TrezorCoinType: Equatable, Hashable {} - -extension TrezorCoinType: Codable {} +extension TrezorTransportErrorCode: Equatable, Hashable {} +extension TrezorTransportErrorCode: Codable {} +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Trezor-related errors exposed via FFI. + * Transport type for Trezor devices. */ -public enum TrezorError: Swift.Error { +public enum TrezorTransportType { - - /** - * Transport layer error (USB/Bluetooth communication) - */ - case TransportError(errorDetails: String - ) - /** - * No Trezor device found - */ - case DeviceNotFound - /** - * Device disconnected during operation - */ - case DeviceDisconnected - /** - * Device is busy and the caller should back off before retrying - */ - case DeviceBusy - /** - * Connection error - */ - case ConnectionError(errorDetails: String - ) - /** - * Protocol error (encoding/decoding) - */ - case ProtocolError(errorDetails: String - ) - /** - * Pairing required for Bluetooth connection - */ - case PairingRequired - /** - * Pairing failed - */ - case PairingFailed(errorDetails: String - ) - /** - * PIN is required - */ - case PinRequired - /** - * PIN entry cancelled - */ - case PinCancelled - /** - * Invalid PIN entered - */ - case InvalidPin - /** - * Passphrase is required - */ - case PassphraseRequired - /** - * Passphrase entry cancelled - */ - case PassphraseCancelled - /** - * Action cancelled by user on device - */ - case UserCancelled - /** - * Operation timed out - */ - case Timeout - /** - * Invalid derivation path - */ - case InvalidPath(errorDetails: String - ) - /** - * Device returned an error - */ - case DeviceError(errorDetails: String - ) - /** - * Trezor manager not initialized - */ - case NotInitialized - /** - * No device connected - */ - case NotConnected /** - * Session error + * USB connection */ - case SessionError(errorDetails: String - ) + case usb /** - * IO error + * Bluetooth connection */ - case IoError(errorDetails: String - ) + case bluetooth } +#if compiler(>=6) +extension TrezorTransportType: Sendable {} +#endif + #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeTrezorError: FfiConverterRustBuffer { - typealias SwiftType = TrezorError +public struct FfiConverterTypeTrezorTransportType: FfiConverterRustBuffer { + typealias SwiftType = TrezorTransportType - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorTransportType { let variant: Int32 = try readInt(&buf) switch variant { - - + case 1: return .usb - case 1: return .TransportError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 2: return .DeviceNotFound - case 3: return .DeviceDisconnected - case 4: return .DeviceBusy - case 5: return .ConnectionError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 6: return .ProtocolError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 7: return .PairingRequired - case 8: return .PairingFailed( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 9: return .PinRequired - case 10: return .PinCancelled - case 11: return .InvalidPin - case 12: return .PassphraseRequired - case 13: return .PassphraseCancelled - case 14: return .UserCancelled - case 15: return .Timeout - case 16: return .InvalidPath( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 17: return .DeviceError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 18: return .NotInitialized - case 19: return .NotConnected - case 20: return .SessionError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - case 21: return .IoError( - errorDetails: try FfiConverterString.read(from: &buf) - ) - - default: throw UniffiInternalError.unexpectedEnumCase + case 2: return .bluetooth + + default: throw UniffiInternalError.unexpectedEnumCase } } - - public static func write(_ value: TrezorError, into buf: inout [UInt8]) { - switch value { - - - - - - case let .TransportError(errorDetails): - writeInt(&buf, Int32(1)) - FfiConverterString.write(errorDetails, into: &buf) - - - case .DeviceNotFound: - writeInt(&buf, Int32(2)) - - - case .DeviceDisconnected: - writeInt(&buf, Int32(3)) - - - case .DeviceBusy: - writeInt(&buf, Int32(4)) - - - case let .ConnectionError(errorDetails): - writeInt(&buf, Int32(5)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .ProtocolError(errorDetails): - writeInt(&buf, Int32(6)) - FfiConverterString.write(errorDetails, into: &buf) - - - case .PairingRequired: - writeInt(&buf, Int32(7)) - - - case let .PairingFailed(errorDetails): - writeInt(&buf, Int32(8)) - FfiConverterString.write(errorDetails, into: &buf) - - - case .PinRequired: - writeInt(&buf, Int32(9)) - - - case .PinCancelled: - writeInt(&buf, Int32(10)) - - - case .InvalidPin: - writeInt(&buf, Int32(11)) - - - case .PassphraseRequired: - writeInt(&buf, Int32(12)) - - - case .PassphraseCancelled: - writeInt(&buf, Int32(13)) - - - case .UserCancelled: - writeInt(&buf, Int32(14)) - - - case .Timeout: - writeInt(&buf, Int32(15)) - - - case let .InvalidPath(errorDetails): - writeInt(&buf, Int32(16)) - FfiConverterString.write(errorDetails, into: &buf) - - - case let .DeviceError(errorDetails): - writeInt(&buf, Int32(17)) - FfiConverterString.write(errorDetails, into: &buf) - - - case .NotInitialized: - writeInt(&buf, Int32(18)) + + public static func write(_ value: TrezorTransportType, into buf: inout [UInt8]) { + switch value { - case .NotConnected: - writeInt(&buf, Int32(19)) + case .usb: + writeInt(&buf, Int32(1)) - case let .SessionError(errorDetails): - writeInt(&buf, Int32(20)) - FfiConverterString.write(errorDetails, into: &buf) - + case .bluetooth: + writeInt(&buf, Int32(2)) - case let .IoError(errorDetails): - writeInt(&buf, Int32(21)) - FfiConverterString.write(errorDetails, into: &buf) - } } } @@ -22796,30 +24075,23 @@ public struct FfiConverterTypeTrezorError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorError_lift(_ buf: RustBuffer) throws -> TrezorError { - return try FfiConverterTypeTrezorError.lift(buf) +public func FfiConverterTypeTrezorTransportType_lift(_ buf: RustBuffer) throws -> TrezorTransportType { + return try FfiConverterTypeTrezorTransportType.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorError_lower(_ value: TrezorError) -> RustBuffer { - return FfiConverterTypeTrezorError.lower(value) +public func FfiConverterTypeTrezorTransportType_lower(_ value: TrezorTransportType) -> RustBuffer { + return FfiConverterTypeTrezorTransportType.lower(value) } -extension TrezorError: Equatable, Hashable {} - -extension TrezorError: Codable {} - +extension TrezorTransportType: Equatable, Hashable {} +extension TrezorTransportType: Codable {} -extension TrezorError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} @@ -22827,95 +24099,176 @@ extension TrezorError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Script types for address derivation. + * Transaction direction from the wallet's perspective. */ -public enum TrezorScriptType { +public enum TxDirection { /** - * P2PKH (legacy) - */ - case spendAddress - /** - * P2SH-P2WPKH (nested SegWit) - */ - case spendP2shWitness - /** - * P2WPKH (native SegWit) - */ - case spendWitness - /** - * P2TR (Taproot) + * Wallet sent funds to an external address */ - case spendTaproot + case sent /** - * P2SH multisig + * Wallet received funds from an external source */ - case spendMultisig + case received /** - * External/watch-only input (not signed by device) + * Wallet sent funds to itself (e.g. consolidation, change-only) */ - case external + case selfTransfer } #if compiler(>=6) -extension TrezorScriptType: Sendable {} +extension TxDirection: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeTrezorScriptType: FfiConverterRustBuffer { - typealias SwiftType = TrezorScriptType +public struct FfiConverterTypeTxDirection: FfiConverterRustBuffer { + typealias SwiftType = TxDirection - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorScriptType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TxDirection { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .spendAddress - - case 2: return .spendP2shWitness - - case 3: return .spendWitness - - case 4: return .spendTaproot + case 1: return .sent - case 5: return .spendMultisig + case 2: return .received - case 6: return .external + case 3: return .selfTransfer default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: TrezorScriptType, into buf: inout [UInt8]) { + public static func write(_ value: TxDirection, into buf: inout [UInt8]) { switch value { - case .spendAddress: + case .sent: writeInt(&buf, Int32(1)) - case .spendP2shWitness: + case .received: writeInt(&buf, Int32(2)) - case .spendWitness: + case .selfTransfer: writeInt(&buf, Int32(3)) + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeTxDirection_lift(_ buf: RustBuffer) throws -> TxDirection { + return try FfiConverterTypeTxDirection.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeTxDirection_lower(_ value: TxDirection) -> RustBuffer { + return FfiConverterTypeTxDirection.lower(value) +} + + +extension TxDirection: Equatable, Hashable {} + +extension TxDirection: Codable {} + + + + + + + +public enum UrError: Swift.Error { + + + + case InvalidUr(reason: String + ) + case TooLarge(reason: String + ) + case InvalidPayload(reason: String + ) + case InvalidPsbt(reason: String + ) + case InvalidPassportExport(reason: String + ) +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeUrError: FfiConverterRustBuffer { + typealias SwiftType = UrError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UrError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + - case .spendTaproot: - writeInt(&buf, Int32(4)) + case 1: return .InvalidUr( + reason: try FfiConverterString.read(from: &buf) + ) + case 2: return .TooLarge( + reason: try FfiConverterString.read(from: &buf) + ) + case 3: return .InvalidPayload( + reason: try FfiConverterString.read(from: &buf) + ) + case 4: return .InvalidPsbt( + reason: try FfiConverterString.read(from: &buf) + ) + case 5: return .InvalidPassportExport( + reason: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: UrError, into buf: inout [UInt8]) { + switch value { + + - case .spendMultisig: - writeInt(&buf, Int32(5)) + case let .InvalidUr(reason): + writeInt(&buf, Int32(1)) + FfiConverterString.write(reason, into: &buf) + - case .external: - writeInt(&buf, Int32(6)) + case let .TooLarge(reason): + writeInt(&buf, Int32(2)) + FfiConverterString.write(reason, into: &buf) + + + case let .InvalidPayload(reason): + writeInt(&buf, Int32(3)) + FfiConverterString.write(reason, into: &buf) + + + case let .InvalidPsbt(reason): + writeInt(&buf, Int32(4)) + FfiConverterString.write(reason, into: &buf) + + case let .InvalidPassportExport(reason): + writeInt(&buf, Int32(5)) + FfiConverterString.write(reason, into: &buf) + } } } @@ -22924,69 +24277,106 @@ public struct FfiConverterTypeTrezorScriptType: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorScriptType_lift(_ buf: RustBuffer) throws -> TrezorScriptType { - return try FfiConverterTypeTrezorScriptType.lift(buf) +public func FfiConverterTypeUrError_lift(_ buf: RustBuffer) throws -> UrError { + return try FfiConverterTypeUrError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorScriptType_lower(_ value: TrezorScriptType) -> RustBuffer { - return FfiConverterTypeTrezorScriptType.lower(value) +public func FfiConverterTypeUrError_lower(_ value: UrError) -> RustBuffer { + return FfiConverterTypeUrError.lower(value) } -extension TrezorScriptType: Equatable, Hashable {} +extension UrError: Equatable, Hashable {} + +extension UrError: Codable {} -extension TrezorScriptType: Codable {} +extension UrError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * Structured transport error code returned by native callback operations. + * A completely decoded UR payload. */ -public enum TrezorTransportErrorCode { +public enum UrPayload { /** - * Device is busy and the caller should back off before retrying. + * The byte string wrapped by a `bytes` registry item. */ - case deviceBusy + case bytes(data: Data + ) + /** + * A `crypto-psbt` registry item, returned in Bitkit's usual base64 form. + */ + case cryptoPsbt(psbt: String + ) + /** + * An uninterpreted UR registry item. + */ + case cbor(urType: String, cbor: Data + ) } #if compiler(>=6) -extension TrezorTransportErrorCode: Sendable {} +extension UrPayload: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeTrezorTransportErrorCode: FfiConverterRustBuffer { - typealias SwiftType = TrezorTransportErrorCode +public struct FfiConverterTypeUrPayload: FfiConverterRustBuffer { + typealias SwiftType = UrPayload - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorTransportErrorCode { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UrPayload { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .deviceBusy + case 1: return .bytes(data: try FfiConverterData.read(from: &buf) + ) + + case 2: return .cryptoPsbt(psbt: try FfiConverterString.read(from: &buf) + ) + + case 3: return .cbor(urType: try FfiConverterString.read(from: &buf), cbor: try FfiConverterData.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: TrezorTransportErrorCode, into buf: inout [UInt8]) { + public static func write(_ value: UrPayload, into buf: inout [UInt8]) { switch value { - case .deviceBusy: + case let .bytes(data): writeInt(&buf, Int32(1)) + FfiConverterData.write(data, into: &buf) + + case let .cryptoPsbt(psbt): + writeInt(&buf, Int32(2)) + FfiConverterString.write(psbt, into: &buf) + + + case let .cbor(urType,cbor): + writeInt(&buf, Int32(3)) + FfiConverterString.write(urType, into: &buf) + FfiConverterData.write(cbor, into: &buf) + } } } @@ -22995,79 +24385,163 @@ public struct FfiConverterTypeTrezorTransportErrorCode: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorTransportErrorCode_lift(_ buf: RustBuffer) throws -> TrezorTransportErrorCode { - return try FfiConverterTypeTrezorTransportErrorCode.lift(buf) +public func FfiConverterTypeUrPayload_lift(_ buf: RustBuffer) throws -> UrPayload { + return try FfiConverterTypeUrPayload.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorTransportErrorCode_lower(_ value: TrezorTransportErrorCode) -> RustBuffer { - return FfiConverterTypeTrezorTransportErrorCode.lower(value) +public func FfiConverterTypeUrPayload_lower(_ value: UrPayload) -> RustBuffer { + return FfiConverterTypeUrPayload.lower(value) } -extension TrezorTransportErrorCode: Equatable, Hashable {} +extension UrPayload: Equatable, Hashable {} -extension TrezorTransportErrorCode: Codable {} +extension UrPayload: Codable {} -// Note that we don't yet support `indirect` for enums. -// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -/** - * Transport type for Trezor devices. - */ -public enum TrezorTransportType { +public enum UsdtError: Swift.Error { + - /** - * USB connection - */ - case usb - /** - * Bluetooth connection - */ - case bluetooth + + case InvalidAmount + case InvalidAddress + case WrongNetwork + case InvalidCredentials + case UnsupportedDelegation + case InsufficientBalance + case QuoteExpired + case PendingTransfer + case UnsupportedRoute + case NotConfigured + case NetworkUnavailable + case RateLimited + case LogRangeTooLarge + case TransactionRejected(reason: String + ) + case Storage(reason: String + ) + case InvalidResponse } -#if compiler(>=6) -extension TrezorTransportType: Sendable {} -#endif - #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeTrezorTransportType: FfiConverterRustBuffer { - typealias SwiftType = TrezorTransportType +public struct FfiConverterTypeUsdtError: FfiConverterRustBuffer { + typealias SwiftType = UsdtError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TrezorTransportType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtError { let variant: Int32 = try readInt(&buf) switch variant { + - case 1: return .usb - - case 2: return .bluetooth + - default: throw UniffiInternalError.unexpectedEnumCase + case 1: return .InvalidAmount + case 2: return .InvalidAddress + case 3: return .WrongNetwork + case 4: return .InvalidCredentials + case 5: return .UnsupportedDelegation + case 6: return .InsufficientBalance + case 7: return .QuoteExpired + case 8: return .PendingTransfer + case 9: return .UnsupportedRoute + case 10: return .NotConfigured + case 11: return .NetworkUnavailable + case 12: return .RateLimited + case 13: return .LogRangeTooLarge + case 14: return .TransactionRejected( + reason: try FfiConverterString.read(from: &buf) + ) + case 15: return .Storage( + reason: try FfiConverterString.read(from: &buf) + ) + case 16: return .InvalidResponse + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: TrezorTransportType, into buf: inout [UInt8]) { + public static func write(_ value: UsdtError, into buf: inout [UInt8]) { switch value { + + - case .usb: + + case .InvalidAmount: writeInt(&buf, Int32(1)) - case .bluetooth: + case .InvalidAddress: writeInt(&buf, Int32(2)) + + case .WrongNetwork: + writeInt(&buf, Int32(3)) + + + case .InvalidCredentials: + writeInt(&buf, Int32(4)) + + + case .UnsupportedDelegation: + writeInt(&buf, Int32(5)) + + + case .InsufficientBalance: + writeInt(&buf, Int32(6)) + + + case .QuoteExpired: + writeInt(&buf, Int32(7)) + + + case .PendingTransfer: + writeInt(&buf, Int32(8)) + + + case .UnsupportedRoute: + writeInt(&buf, Int32(9)) + + + case .NotConfigured: + writeInt(&buf, Int32(10)) + + + case .NetworkUnavailable: + writeInt(&buf, Int32(11)) + + + case .RateLimited: + writeInt(&buf, Int32(12)) + + + case .LogRangeTooLarge: + writeInt(&buf, Int32(13)) + + + case let .TransactionRejected(reason): + writeInt(&buf, Int32(14)) + FfiConverterString.write(reason, into: &buf) + + + case let .Storage(reason): + writeInt(&buf, Int32(15)) + FfiConverterString.write(reason, into: &buf) + + + case .InvalidResponse: + writeInt(&buf, Int32(16)) + } } } @@ -23076,89 +24550,103 @@ public struct FfiConverterTypeTrezorTransportType: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorTransportType_lift(_ buf: RustBuffer) throws -> TrezorTransportType { - return try FfiConverterTypeTrezorTransportType.lift(buf) +public func FfiConverterTypeUsdtError_lift(_ buf: RustBuffer) throws -> UsdtError { + return try FfiConverterTypeUsdtError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTrezorTransportType_lower(_ value: TrezorTransportType) -> RustBuffer { - return FfiConverterTypeTrezorTransportType.lower(value) +public func FfiConverterTypeUsdtError_lower(_ value: UsdtError) -> RustBuffer { + return FfiConverterTypeUsdtError.lower(value) } -extension TrezorTransportType: Equatable, Hashable {} +extension UsdtError: Equatable, Hashable {} + +extension UsdtError: Codable {} + -extension TrezorTransportType: Codable {} +extension UsdtError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. -/** - * Transaction direction from the wallet's perspective. - */ -public enum TxDirection { +public enum UsdtTransferStatus { /** - * Wallet sent funds to an external address + * Signed payment awaiting a conclusive source-chain outcome. */ - case sent + case pending /** - * Wallet received funds from an external source + * Payment received on its destination chain. */ - case received + case confirmed /** - * Wallet sent funds to itself (e.g. consolidation, change-only) + * Source payment failed or was proven not to have executed. */ - case selfTransfer + case failed + /** + * Another operation consumed the payment nonce. + */ + case replaced } #if compiler(>=6) -extension TxDirection: Sendable {} +extension UsdtTransferStatus: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeTxDirection: FfiConverterRustBuffer { - typealias SwiftType = TxDirection +public struct FfiConverterTypeUsdtTransferStatus: FfiConverterRustBuffer { + typealias SwiftType = UsdtTransferStatus - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TxDirection { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtTransferStatus { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .sent + case 1: return .pending - case 2: return .received + case 2: return .confirmed - case 3: return .selfTransfer + case 3: return .failed + + case 4: return .replaced default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: TxDirection, into buf: inout [UInt8]) { + public static func write(_ value: UsdtTransferStatus, into buf: inout [UInt8]) { switch value { - case .sent: + case .pending: writeInt(&buf, Int32(1)) - case .received: + case .confirmed: writeInt(&buf, Int32(2)) - case .selfTransfer: + case .failed: writeInt(&buf, Int32(3)) + + case .replaced: + writeInt(&buf, Int32(4)) + } } } @@ -23167,109 +24655,89 @@ public struct FfiConverterTypeTxDirection: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTxDirection_lift(_ buf: RustBuffer) throws -> TxDirection { - return try FfiConverterTypeTxDirection.lift(buf) +public func FfiConverterTypeUsdtTransferStatus_lift(_ buf: RustBuffer) throws -> UsdtTransferStatus { + return try FfiConverterTypeUsdtTransferStatus.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeTxDirection_lower(_ value: TxDirection) -> RustBuffer { - return FfiConverterTypeTxDirection.lower(value) +public func FfiConverterTypeUsdtTransferStatus_lower(_ value: UsdtTransferStatus) -> RustBuffer { + return FfiConverterTypeUsdtTransferStatus.lower(value) } -extension TxDirection: Equatable, Hashable {} - -extension TxDirection: Codable {} +extension UsdtTransferStatus: Equatable, Hashable {} +extension UsdtTransferStatus: Codable {} -public enum UrError: Swift.Error { +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * What the device did with a wake. + */ +public enum WakeAckOutcome { - - case InvalidUr(reason: String - ) - case TooLarge(reason: String - ) - case InvalidPayload(reason: String - ) - case InvalidPsbt(reason: String - ) - case InvalidPassportExport(reason: String - ) + /** + * The wake was handled; the gateway cancels its fallback alert. + */ + case handled + /** + * The app has no handler for the topic; the fallback alert still fires. + */ + case noHandler + /** + * Handling failed; the fallback alert still fires. + */ + case failed } +#if compiler(>=6) +extension WakeAckOutcome: Sendable {} +#endif + #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeUrError: FfiConverterRustBuffer { - typealias SwiftType = UrError +public struct FfiConverterTypeWakeAckOutcome: FfiConverterRustBuffer { + typealias SwiftType = WakeAckOutcome - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UrError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeAckOutcome { let variant: Int32 = try readInt(&buf) switch variant { - - + case 1: return .handled - case 1: return .InvalidUr( - reason: try FfiConverterString.read(from: &buf) - ) - case 2: return .TooLarge( - reason: try FfiConverterString.read(from: &buf) - ) - case 3: return .InvalidPayload( - reason: try FfiConverterString.read(from: &buf) - ) - case 4: return .InvalidPsbt( - reason: try FfiConverterString.read(from: &buf) - ) - case 5: return .InvalidPassportExport( - reason: try FfiConverterString.read(from: &buf) - ) - - default: throw UniffiInternalError.unexpectedEnumCase + case 2: return .noHandler + + case 3: return .failed + + default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: UrError, into buf: inout [UInt8]) { + public static func write(_ value: WakeAckOutcome, into buf: inout [UInt8]) { switch value { - - - - case let .InvalidUr(reason): + case .handled: writeInt(&buf, Int32(1)) - FfiConverterString.write(reason, into: &buf) - - case let .TooLarge(reason): + + case .noHandler: writeInt(&buf, Int32(2)) - FfiConverterString.write(reason, into: &buf) - - case let .InvalidPayload(reason): - writeInt(&buf, Int32(3)) - FfiConverterString.write(reason, into: &buf) - - case let .InvalidPsbt(reason): - writeInt(&buf, Int32(4)) - FfiConverterString.write(reason, into: &buf) - + case .failed: + writeInt(&buf, Int32(3)) - case let .InvalidPassportExport(reason): - writeInt(&buf, Int32(5)) - FfiConverterString.write(reason, into: &buf) - } } } @@ -23278,30 +24746,23 @@ public struct FfiConverterTypeUrError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUrError_lift(_ buf: RustBuffer) throws -> UrError { - return try FfiConverterTypeUrError.lift(buf) +public func FfiConverterTypeWakeAckOutcome_lift(_ buf: RustBuffer) throws -> WakeAckOutcome { + return try FfiConverterTypeWakeAckOutcome.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUrError_lower(_ value: UrError) -> RustBuffer { - return FfiConverterTypeUrError.lower(value) +public func FfiConverterTypeWakeAckOutcome_lower(_ value: WakeAckOutcome) -> RustBuffer { + return FfiConverterTypeWakeAckOutcome.lower(value) } -extension UrError: Equatable, Hashable {} - -extension UrError: Codable {} - +extension WakeAckOutcome: Equatable, Hashable {} +extension WakeAckOutcome: Codable {} -extension UrError: Foundation.LocalizedError { - public var errorDescription: String? { - String(reflecting: self) - } -} @@ -23309,75 +24770,49 @@ extension UrError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. /** - * A completely decoded UR payload. + * APNs environment. FCM devices are always `Production`. */ -public enum UrPayload { +public enum WakeEnvironment { - /** - * The byte string wrapped by a `bytes` registry item. - */ - case bytes(data: Data - ) - /** - * A `crypto-psbt` registry item, returned in Bitkit's usual base64 form. - */ - case cryptoPsbt(psbt: String - ) - /** - * An uninterpreted UR registry item. - */ - case cbor(urType: String, cbor: Data - ) + case production + case sandbox } #if compiler(>=6) -extension UrPayload: Sendable {} +extension WakeEnvironment: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeUrPayload: FfiConverterRustBuffer { - typealias SwiftType = UrPayload +public struct FfiConverterTypeWakeEnvironment: FfiConverterRustBuffer { + typealias SwiftType = WakeEnvironment - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UrPayload { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeEnvironment { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .bytes(data: try FfiConverterData.read(from: &buf) - ) - - case 2: return .cryptoPsbt(psbt: try FfiConverterString.read(from: &buf) - ) + case 1: return .production - case 3: return .cbor(urType: try FfiConverterString.read(from: &buf), cbor: try FfiConverterData.read(from: &buf) - ) + case 2: return .sandbox default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: UrPayload, into buf: inout [UInt8]) { + public static func write(_ value: WakeEnvironment, into buf: inout [UInt8]) { switch value { - case let .bytes(data): + case .production: writeInt(&buf, Int32(1)) - FfiConverterData.write(data, into: &buf) - - case let .cryptoPsbt(psbt): + + case .sandbox: writeInt(&buf, Int32(2)) - FfiConverterString.write(psbt, into: &buf) - - case let .cbor(urType,cbor): - writeInt(&buf, Int32(3)) - FfiConverterString.write(urType, into: &buf) - FfiConverterData.write(cbor, into: &buf) - } } } @@ -23386,21 +24821,21 @@ public struct FfiConverterTypeUrPayload: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUrPayload_lift(_ buf: RustBuffer) throws -> UrPayload { - return try FfiConverterTypeUrPayload.lift(buf) +public func FfiConverterTypeWakeEnvironment_lift(_ buf: RustBuffer) throws -> WakeEnvironment { + return try FfiConverterTypeWakeEnvironment.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUrPayload_lower(_ value: UrPayload) -> RustBuffer { - return FfiConverterTypeUrPayload.lower(value) +public func FfiConverterTypeWakeEnvironment_lower(_ value: WakeEnvironment) -> RustBuffer { + return FfiConverterTypeWakeEnvironment.lower(value) } -extension UrPayload: Equatable, Hashable {} +extension WakeEnvironment: Equatable, Hashable {} -extension UrPayload: Codable {} +extension WakeEnvironment: Codable {} @@ -23408,141 +24843,115 @@ extension UrPayload: Codable {} -public enum UsdtError: Swift.Error { +/** + * Errors of the wake module. Messages never contain secrets, push tokens or + * decrypted plaintext. + */ +public enum WakeError: Swift.Error { - case InvalidAmount - case InvalidAddress - case WrongNetwork - case InvalidCredentials - case UnsupportedDelegation - case InsufficientBalance - case QuoteExpired - case PendingTransfer - case UnsupportedRoute - case NotConfigured - case NetworkUnavailable - case RateLimited - case LogRangeTooLarge - case TransactionRejected(reason: String + case InvalidInput(reason: String ) - case Storage(reason: String + case InvalidKey(reason: String + ) + case DecryptionFailed(reason: String + ) + case UnsupportedEnvelope(reason: String + ) + case RequestFailed(reason: String + ) + case GatewayRejected(status: UInt16, code: String, detail: String + ) + case InvalidResponse(reason: String ) - case InvalidResponse } #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeUsdtError: FfiConverterRustBuffer { - typealias SwiftType = UsdtError +public struct FfiConverterTypeWakeError: FfiConverterRustBuffer { + typealias SwiftType = WakeError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakeError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .InvalidAmount - case 2: return .InvalidAddress - case 3: return .WrongNetwork - case 4: return .InvalidCredentials - case 5: return .UnsupportedDelegation - case 6: return .InsufficientBalance - case 7: return .QuoteExpired - case 8: return .PendingTransfer - case 9: return .UnsupportedRoute - case 10: return .NotConfigured - case 11: return .NetworkUnavailable - case 12: return .RateLimited - case 13: return .LogRangeTooLarge - case 14: return .TransactionRejected( + case 1: return .InvalidInput( reason: try FfiConverterString.read(from: &buf) ) - case 15: return .Storage( + case 2: return .InvalidKey( + reason: try FfiConverterString.read(from: &buf) + ) + case 3: return .DecryptionFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 4: return .UnsupportedEnvelope( + reason: try FfiConverterString.read(from: &buf) + ) + case 5: return .RequestFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 6: return .GatewayRejected( + status: try FfiConverterUInt16.read(from: &buf), + code: try FfiConverterString.read(from: &buf), + detail: try FfiConverterString.read(from: &buf) + ) + case 7: return .InvalidResponse( reason: try FfiConverterString.read(from: &buf) ) - case 16: return .InvalidResponse default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: UsdtError, into buf: inout [UInt8]) { + public static func write(_ value: WakeError, into buf: inout [UInt8]) { switch value { - case .InvalidAmount: + case let .InvalidInput(reason): writeInt(&buf, Int32(1)) + FfiConverterString.write(reason, into: &buf) + - - case .InvalidAddress: + case let .InvalidKey(reason): writeInt(&buf, Int32(2)) + FfiConverterString.write(reason, into: &buf) + - - case .WrongNetwork: + case let .DecryptionFailed(reason): writeInt(&buf, Int32(3)) + FfiConverterString.write(reason, into: &buf) + - - case .InvalidCredentials: + case let .UnsupportedEnvelope(reason): writeInt(&buf, Int32(4)) + FfiConverterString.write(reason, into: &buf) + - - case .UnsupportedDelegation: + case let .RequestFailed(reason): writeInt(&buf, Int32(5)) + FfiConverterString.write(reason, into: &buf) + - - case .InsufficientBalance: + case let .GatewayRejected(status,code,detail): writeInt(&buf, Int32(6)) - - - case .QuoteExpired: - writeInt(&buf, Int32(7)) - - - case .PendingTransfer: - writeInt(&buf, Int32(8)) - - - case .UnsupportedRoute: - writeInt(&buf, Int32(9)) - - - case .NotConfigured: - writeInt(&buf, Int32(10)) - - - case .NetworkUnavailable: - writeInt(&buf, Int32(11)) - - - case .RateLimited: - writeInt(&buf, Int32(12)) - - - case .LogRangeTooLarge: - writeInt(&buf, Int32(13)) - - - case let .TransactionRejected(reason): - writeInt(&buf, Int32(14)) - FfiConverterString.write(reason, into: &buf) + FfiConverterUInt16.write(status, into: &buf) + FfiConverterString.write(code, into: &buf) + FfiConverterString.write(detail, into: &buf) - case let .Storage(reason): - writeInt(&buf, Int32(15)) + case let .InvalidResponse(reason): + writeInt(&buf, Int32(7)) FfiConverterString.write(reason, into: &buf) - - case .InvalidResponse: - writeInt(&buf, Int32(16)) - } } } @@ -23551,26 +24960,26 @@ public struct FfiConverterTypeUsdtError: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUsdtError_lift(_ buf: RustBuffer) throws -> UsdtError { - return try FfiConverterTypeUsdtError.lift(buf) +public func FfiConverterTypeWakeError_lift(_ buf: RustBuffer) throws -> WakeError { + return try FfiConverterTypeWakeError.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUsdtError_lower(_ value: UsdtError) -> RustBuffer { - return FfiConverterTypeUsdtError.lower(value) +public func FfiConverterTypeWakeError_lower(_ value: WakeError) -> RustBuffer { + return FfiConverterTypeWakeError.lower(value) } -extension UsdtError: Equatable, Hashable {} +extension WakeError: Equatable, Hashable {} -extension UsdtError: Codable {} +extension WakeError: Codable {} -extension UsdtError: Foundation.LocalizedError { +extension WakeError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -23581,73 +24990,50 @@ extension UsdtError: Foundation.LocalizedError { // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Push transport of a device. + */ -public enum UsdtTransferStatus { +public enum WakePlatform { - /** - * Signed payment awaiting a conclusive source-chain outcome. - */ - case pending - /** - * Payment received on its destination chain. - */ - case confirmed - /** - * Source payment failed or was proven not to have executed. - */ - case failed - /** - * Another operation consumed the payment nonce. - */ - case replaced + case apns + case fcm } #if compiler(>=6) -extension UsdtTransferStatus: Sendable {} +extension WakePlatform: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeUsdtTransferStatus: FfiConverterRustBuffer { - typealias SwiftType = UsdtTransferStatus +public struct FfiConverterTypeWakePlatform: FfiConverterRustBuffer { + typealias SwiftType = WakePlatform - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UsdtTransferStatus { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> WakePlatform { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .pending - - case 2: return .confirmed - - case 3: return .failed + case 1: return .apns - case 4: return .replaced + case 2: return .fcm default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: UsdtTransferStatus, into buf: inout [UInt8]) { + public static func write(_ value: WakePlatform, into buf: inout [UInt8]) { switch value { - case .pending: + case .apns: writeInt(&buf, Int32(1)) - case .confirmed: + case .fcm: writeInt(&buf, Int32(2)) - - case .failed: - writeInt(&buf, Int32(3)) - - - case .replaced: - writeInt(&buf, Int32(4)) - } } } @@ -23656,21 +25042,21 @@ public struct FfiConverterTypeUsdtTransferStatus: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUsdtTransferStatus_lift(_ buf: RustBuffer) throws -> UsdtTransferStatus { - return try FfiConverterTypeUsdtTransferStatus.lift(buf) +public func FfiConverterTypeWakePlatform_lift(_ buf: RustBuffer) throws -> WakePlatform { + return try FfiConverterTypeWakePlatform.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeUsdtTransferStatus_lower(_ value: UsdtTransferStatus) -> RustBuffer { - return FfiConverterTypeUsdtTransferStatus.lower(value) +public func FfiConverterTypeWakePlatform_lower(_ value: WakePlatform) -> RustBuffer { + return FfiConverterTypeWakePlatform.lower(value) } -extension UsdtTransferStatus: Equatable, Hashable {} +extension WakePlatform: Equatable, Hashable {} -extension UsdtTransferStatus: Codable {} +extension WakePlatform: Codable {} @@ -26205,6 +27591,56 @@ fileprivate struct FfiConverterSequenceTypeUsdtTransfer: FfiConverterRustBuffer } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeWakeIdentityProof: FfiConverterRustBuffer { + typealias SwiftType = [WakeIdentityProof] + + public static func write(_ value: [WakeIdentityProof], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeWakeIdentityProof.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [WakeIdentityProof] { + let len: Int32 = try readInt(&buf) + var seq = [WakeIdentityProof]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeWakeIdentityProof.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeWakeTopic: FfiConverterRustBuffer { + typealias SwiftType = [WakeTopic] + + public static func write(_ value: [WakeTopic], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeWakeTopic.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [WakeTopic] { + let len: Int32 = try readInt(&buf) + var seq = [WakeTopic]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeWakeTopic.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -29028,6 +30464,237 @@ public func validateMnemonic(mnemonicPhrase: String)throws {try rustCallWithEr ) } } +/** + * Acknowledge a wake by the id from its envelope. + */ +public func wakeAck(gatewayUrl: String, deviceSecret: String, wakeId: String, outcome: WakeAckOutcome)async throws { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_wake_ack(FfiConverterString.lower(gatewayUrl),FfiConverterString.lower(deviceSecret),FfiConverterString.lower(wakeId),FfiConverterTypeWakeAckOutcome_lower(outcome) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_void, + completeFunc: ffi_bitkitcore_rust_future_complete_void, + freeFunc: ffi_bitkitcore_rust_future_free_void, + liftFunc: { $0 }, + errorHandler: FfiConverterTypeWakeError_lift + ) +} +/** + * End presence early. + */ +public func wakeClearPresence(gatewayUrl: String, deviceSecret: String)async throws { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_wake_clear_presence(FfiConverterString.lower(gatewayUrl),FfiConverterString.lower(deviceSecret) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_void, + completeFunc: ffi_bitkitcore_rust_future_complete_void, + freeFunc: ffi_bitkitcore_rust_future_free_void, + liftFunc: { $0 }, + errorHandler: FfiConverterTypeWakeError_lift + ) +} +/** + * Find and decrypt the wake in a delivered push (the APNs payload or the FCM data map as JSON). + */ +public func wakeDecryptPush(secretKeyHex: String, pushJson: String)throws -> WakeEnvelope { + return try FfiConverterTypeWakeEnvelope_lift(try rustCallWithError(FfiConverterTypeWakeError_lift) { + uniffi_bitkitcore_fn_func_wake_decrypt_push( + FfiConverterString.lower(secretKeyHex), + FfiConverterString.lower(pushJson),$0 + ) +}) +} +/** + * Decrypt a legacy (v0) envelope from its four fields. + */ +public func wakeDecryptV0(secretKeyHex: String, cipher: String, iv: String, tag: String, publicKey: String)throws -> WakeEnvelope { + return try FfiConverterTypeWakeEnvelope_lift(try rustCallWithError(FfiConverterTypeWakeError_lift) { + uniffi_bitkitcore_fn_func_wake_decrypt_v0( + FfiConverterString.lower(secretKeyHex), + FfiConverterString.lower(cipher), + FfiConverterString.lower(iv), + FfiConverterString.lower(tag), + FfiConverterString.lower(publicKey),$0 + ) +}) +} +/** + * Decrypt a v1 container given as JSON. + */ +public func wakeDecryptV1(secretKeyHex: String, containerJson: String)throws -> WakeEnvelope { + return try FfiConverterTypeWakeEnvelope_lift(try rustCallWithError(FfiConverterTypeWakeError_lift) { + uniffi_bitkitcore_fn_func_wake_decrypt_v1( + FfiConverterString.lower(secretKeyHex), + FfiConverterString.lower(containerJson),$0 + ) +}) +} +/** + * Generate a device secret (`wkd_...`), the bearer credential of the device routes. + */ +public func wakeGenerateDeviceSecret()throws -> WakeDeviceSecret { + return try FfiConverterTypeWakeDeviceSecret_lift(try rustCallWithError(FfiConverterTypeWakeError_lift) { + uniffi_bitkitcore_fn_func_wake_generate_device_secret($0 + ) +}) +} +/** + * Generate an install id (22 base64url characters), stable for the life of the install. + */ +public func wakeGenerateInstallId() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_bitkitcore_fn_func_wake_generate_install_id($0 + ) +}) +} +/** + * Generate the secp256k1 key pair wakes are encrypted to. + */ +public func wakeGenerateKeypair()throws -> WakeKeyPair { + return try FfiConverterTypeWakeKeyPair_lift(try rustCallWithError(FfiConverterTypeWakeError_lift) { + uniffi_bitkitcore_fn_func_wake_generate_keypair($0 + ) +}) +} +/** + * List the topics devices can subscribe to. + */ +public func wakeListTopics(gatewayUrl: String)async throws -> [WakeTopic] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_wake_list_topics(FfiConverterString.lower(gatewayUrl) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceTypeWakeTopic.lift, + errorHandler: FfiConverterTypeWakeError_lift + ) +} +/** + * Validate a registration and build the message every identity signs. + * `timestamp` defaults to now. + */ +public func wakePrepareRegistration(audience: String, app: String, installId: String, platform: WakePlatform, environment: WakeEnvironment, pushToken: String, encryptionPublicKey: String, secretSha256: String, identities: [String], topics: [String], timestamp: UInt64?)throws -> WakeRegistrationRequest { + return try FfiConverterTypeWakeRegistrationRequest_lift(try rustCallWithError(FfiConverterTypeWakeError_lift) { + uniffi_bitkitcore_fn_func_wake_prepare_registration( + FfiConverterString.lower(audience), + FfiConverterString.lower(app), + FfiConverterString.lower(installId), + FfiConverterTypeWakePlatform_lower(platform), + FfiConverterTypeWakeEnvironment_lower(environment), + FfiConverterString.lower(pushToken), + FfiConverterString.lower(encryptionPublicKey), + FfiConverterString.lower(secretSha256), + FfiConverterSequenceString.lower(identities), + FfiConverterSequenceString.lower(topics), + FfiConverterOptionUInt64.lower(timestamp),$0 + ) +}) +} +/** + * Register the device, or update its registration, with one proof per identity. + */ +public func wakeRegister(gatewayUrl: String, request: WakeRegistrationRequest, proofs: [WakeIdentityProof])async throws -> WakeRegistration { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_wake_register(FfiConverterString.lower(gatewayUrl),FfiConverterTypeWakeRegistrationRequest_lower(request),FfiConverterSequenceTypeWakeIdentityProof.lower(proofs) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeWakeRegistration_lift, + errorHandler: FfiConverterTypeWakeError_lift + ) +} +/** + * Read the gateway's audience, clock and version. + */ +public func wakeServerInfo(gatewayUrl: String)async throws -> WakeServerInfo { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_wake_server_info(FfiConverterString.lower(gatewayUrl) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeWakeServerInfo_lift, + errorHandler: FfiConverterTypeWakeError_lift + ) +} +/** + * Mark the app as in use for `ttl_secs` (clamped to 15..=300). Returns the expiry, unix seconds. + */ +public func wakeSetPresence(gatewayUrl: String, deviceSecret: String, ttlSecs: UInt32)async throws -> UInt64 { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_wake_set_presence(FfiConverterString.lower(gatewayUrl),FfiConverterString.lower(deviceSecret),FfiConverterUInt32.lower(ttlSecs) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_u64, + completeFunc: ffi_bitkitcore_rust_future_complete_u64, + freeFunc: ffi_bitkitcore_rust_future_free_u64, + liftFunc: FfiConverterUInt64.lift, + errorHandler: FfiConverterTypeWakeError_lift + ) +} +/** + * Replace the device's topics. Returns the stored topics; unknown names are dropped. + */ +public func wakeSetTopics(gatewayUrl: String, deviceSecret: String, topics: [String])async throws -> [String] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_wake_set_topics(FfiConverterString.lower(gatewayUrl),FfiConverterString.lower(deviceSecret),FfiConverterSequenceString.lower(topics) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceString.lift, + errorHandler: FfiConverterTypeWakeError_lift + ) +} +/** + * Sign a registration message with a pubky secret key (hex). Returns 128 hex characters. + */ +public func wakeSignPubkyProof(secretKeyHex: String, message: String)throws -> String { + return try FfiConverterString.lift(try rustCallWithError(FfiConverterTypeWakeError_lift) { + uniffi_bitkitcore_fn_func_wake_sign_pubky_proof( + FfiConverterString.lower(secretKeyHex), + FfiConverterString.lower(message),$0 + ) +}) +} +/** + * Unregister the device and revoke its secret. + */ +public func wakeUnregister(gatewayUrl: String, deviceSecret: String)async throws { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_wake_unregister(FfiConverterString.lower(gatewayUrl),FfiConverterString.lower(deviceSecret) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_void, + completeFunc: ffi_bitkitcore_rust_future_complete_void, + freeFunc: ffi_bitkitcore_rust_future_free_void, + liftFunc: { $0 }, + errorHandler: FfiConverterTypeWakeError_lift + ) +} public func wipeAllClosedChannels()throws {try rustCallWithError(FfiConverterTypeActivityError_lift) { uniffi_bitkitcore_fn_func_wipe_all_closed_channels($0 ) @@ -29683,6 +31350,54 @@ private let initializationResult: InitializationResult = { if (uniffi_bitkitcore_checksum_func_validate_mnemonic() != 31005) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_func_wake_ack() != 21924) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_clear_presence() != 38792) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_decrypt_push() != 65285) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_decrypt_v0() != 47601) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_decrypt_v1() != 33747) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_generate_device_secret() != 48167) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_generate_install_id() != 8335) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_generate_keypair() != 41918) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_list_topics() != 5582) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_prepare_registration() != 32376) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_register() != 32128) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_server_info() != 21220) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_set_presence() != 25525) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_set_topics() != 27232) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_sign_pubky_proof() != 15089) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_bitkitcore_checksum_func_wake_unregister() != 56421) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_func_wipe_all_closed_channels() != 41511) { return InitializationResult.apiChecksumMismatch } diff --git a/bindings/ios/bitkitcoreFFI.h b/bindings/ios/bitkitcoreFFI.h index 63b655a..4eb609a 100644 --- a/bindings/ios/bitkitcoreFFI.h +++ b/bindings/ios/bitkitcoreFFI.h @@ -1784,6 +1784,89 @@ RustBuffer uniffi_bitkitcore_fn_func_validate_bitcoin_address(RustBuffer address void uniffi_bitkitcore_fn_func_validate_mnemonic(RustBuffer mnemonic_phrase, RustCallStatus *_Nonnull out_status ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_ACK +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_ACK +uint64_t uniffi_bitkitcore_fn_func_wake_ack(RustBuffer gateway_url, RustBuffer device_secret, RustBuffer wake_id, RustBuffer outcome +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_CLEAR_PRESENCE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_CLEAR_PRESENCE +uint64_t uniffi_bitkitcore_fn_func_wake_clear_presence(RustBuffer gateway_url, RustBuffer device_secret +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_DECRYPT_PUSH +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_DECRYPT_PUSH +RustBuffer uniffi_bitkitcore_fn_func_wake_decrypt_push(RustBuffer secret_key_hex, RustBuffer push_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_DECRYPT_V0 +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_DECRYPT_V0 +RustBuffer uniffi_bitkitcore_fn_func_wake_decrypt_v0(RustBuffer secret_key_hex, RustBuffer cipher, RustBuffer iv, RustBuffer tag, RustBuffer public_key, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_DECRYPT_V1 +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_DECRYPT_V1 +RustBuffer uniffi_bitkitcore_fn_func_wake_decrypt_v1(RustBuffer secret_key_hex, RustBuffer container_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_GENERATE_DEVICE_SECRET +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_GENERATE_DEVICE_SECRET +RustBuffer uniffi_bitkitcore_fn_func_wake_generate_device_secret(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_GENERATE_INSTALL_ID +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_GENERATE_INSTALL_ID +RustBuffer uniffi_bitkitcore_fn_func_wake_generate_install_id(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_GENERATE_KEYPAIR +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_GENERATE_KEYPAIR +RustBuffer uniffi_bitkitcore_fn_func_wake_generate_keypair(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_LIST_TOPICS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_LIST_TOPICS +uint64_t uniffi_bitkitcore_fn_func_wake_list_topics(RustBuffer gateway_url +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_PREPARE_REGISTRATION +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_PREPARE_REGISTRATION +RustBuffer uniffi_bitkitcore_fn_func_wake_prepare_registration(RustBuffer audience, RustBuffer app, RustBuffer install_id, RustBuffer platform, RustBuffer environment, RustBuffer push_token, RustBuffer encryption_public_key, RustBuffer secret_sha256, RustBuffer identities, RustBuffer topics, RustBuffer timestamp, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_REGISTER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_REGISTER +uint64_t uniffi_bitkitcore_fn_func_wake_register(RustBuffer gateway_url, RustBuffer request, RustBuffer proofs +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_SERVER_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_SERVER_INFO +uint64_t uniffi_bitkitcore_fn_func_wake_server_info(RustBuffer gateway_url +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_SET_PRESENCE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_SET_PRESENCE +uint64_t uniffi_bitkitcore_fn_func_wake_set_presence(RustBuffer gateway_url, RustBuffer device_secret, uint32_t ttl_secs +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_SET_TOPICS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_SET_TOPICS +uint64_t uniffi_bitkitcore_fn_func_wake_set_topics(RustBuffer gateway_url, RustBuffer device_secret, RustBuffer topics +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_SIGN_PUBKY_PROOF +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_SIGN_PUBKY_PROOF +RustBuffer uniffi_bitkitcore_fn_func_wake_sign_pubky_proof(RustBuffer secret_key_hex, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_UNREGISTER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WAKE_UNREGISTER +uint64_t uniffi_bitkitcore_fn_func_wake_unregister(RustBuffer gateway_url, RustBuffer device_secret +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WIPE_ALL_CLOSED_CHANNELS #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_WIPE_ALL_CLOSED_CHANNELS void uniffi_bitkitcore_fn_func_wipe_all_closed_channels(RustCallStatus *_Nonnull out_status @@ -3310,6 +3393,102 @@ uint16_t uniffi_bitkitcore_checksum_func_validate_bitcoin_address(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_VALIDATE_MNEMONIC uint16_t uniffi_bitkitcore_checksum_func_validate_mnemonic(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_ACK +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_ACK +uint16_t uniffi_bitkitcore_checksum_func_wake_ack(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_CLEAR_PRESENCE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_CLEAR_PRESENCE +uint16_t uniffi_bitkitcore_checksum_func_wake_clear_presence(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_DECRYPT_PUSH +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_DECRYPT_PUSH +uint16_t uniffi_bitkitcore_checksum_func_wake_decrypt_push(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_DECRYPT_V0 +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_DECRYPT_V0 +uint16_t uniffi_bitkitcore_checksum_func_wake_decrypt_v0(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_DECRYPT_V1 +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_DECRYPT_V1 +uint16_t uniffi_bitkitcore_checksum_func_wake_decrypt_v1(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_GENERATE_DEVICE_SECRET +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_GENERATE_DEVICE_SECRET +uint16_t uniffi_bitkitcore_checksum_func_wake_generate_device_secret(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_GENERATE_INSTALL_ID +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_GENERATE_INSTALL_ID +uint16_t uniffi_bitkitcore_checksum_func_wake_generate_install_id(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_GENERATE_KEYPAIR +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_GENERATE_KEYPAIR +uint16_t uniffi_bitkitcore_checksum_func_wake_generate_keypair(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_LIST_TOPICS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_LIST_TOPICS +uint16_t uniffi_bitkitcore_checksum_func_wake_list_topics(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_PREPARE_REGISTRATION +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_PREPARE_REGISTRATION +uint16_t uniffi_bitkitcore_checksum_func_wake_prepare_registration(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_REGISTER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_REGISTER +uint16_t uniffi_bitkitcore_checksum_func_wake_register(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_SERVER_INFO +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_SERVER_INFO +uint16_t uniffi_bitkitcore_checksum_func_wake_server_info(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_SET_PRESENCE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_SET_PRESENCE +uint16_t uniffi_bitkitcore_checksum_func_wake_set_presence(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_SET_TOPICS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_SET_TOPICS +uint16_t uniffi_bitkitcore_checksum_func_wake_set_topics(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_SIGN_PUBKY_PROOF +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_SIGN_PUBKY_PROOF +uint16_t uniffi_bitkitcore_checksum_func_wake_sign_pubky_proof(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_UNREGISTER +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WAKE_UNREGISTER +uint16_t uniffi_bitkitcore_checksum_func_wake_unregister(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_WIPE_ALL_CLOSED_CHANNELS diff --git a/src/lib.rs b/src/lib.rs index 035fb1d..ba763a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,11 @@ pub use crate::modules::ur::{ passport_parse_account_export, ur_encode_crypto_psbt, PassportAccount, PassportAccountExport, UrDecoder, UrDecoderStatus, UrError, UrPayload, }; +use crate::modules::wake::{ + WakeAckOutcome, WakeDeviceSecret, WakeEnvelope, WakeEnvironment, WakeError, WakeIdentityProof, + WakeKeyPair, WakePlatform, WakeRegistration, WakeRegistrationRequest, WakeServerInfo, + WakeTopic, +}; pub use crate::onchain::WordCount; use crate::onchain::{ broadcast_raw_tx, get_account_info, get_address_info, get_transaction_detail, @@ -2297,6 +2302,228 @@ pub async fn pubky_session_list( }) } +// ============================================================================ +// Wake Functions +// ============================================================================ + +/// Generate the secp256k1 key pair wakes are encrypted to. +#[uniffi::export] +pub fn wake_generate_keypair() -> Result { + crate::modules::wake::wake_generate_keypair() +} + +/// Generate a device secret (`wkd_...`), the bearer credential of the device routes. +#[uniffi::export] +pub fn wake_generate_device_secret() -> Result { + crate::modules::wake::wake_generate_device_secret() +} + +/// Generate an install id (22 base64url characters), stable for the life of the install. +#[uniffi::export] +pub fn wake_generate_install_id() -> String { + crate::modules::wake::wake_generate_install_id() +} + +/// Validate a registration and build the message every identity signs. +/// `timestamp` defaults to now. +#[uniffi::export] +#[allow(clippy::too_many_arguments)] +pub fn wake_prepare_registration( + audience: String, + app: String, + install_id: String, + platform: WakePlatform, + environment: WakeEnvironment, + push_token: String, + encryption_public_key: String, + secret_sha256: String, + identities: Vec, + topics: Vec, + timestamp: Option, +) -> Result { + crate::modules::wake::wake_prepare_registration( + audience, + app, + install_id, + platform, + environment, + push_token, + encryption_public_key, + secret_sha256, + identities, + topics, + timestamp, + ) +} + +/// Sign a registration message with a pubky secret key (hex). Returns 128 hex characters. +#[uniffi::export] +pub fn wake_sign_pubky_proof(secret_key_hex: String, message: String) -> Result { + crate::modules::wake::wake_sign_pubky_proof(secret_key_hex, message) +} + +/// Find and decrypt the wake in a delivered push (the APNs payload or the FCM data map as JSON). +#[uniffi::export] +pub fn wake_decrypt_push( + secret_key_hex: String, + push_json: String, +) -> Result { + crate::modules::wake::wake_decrypt_push(secret_key_hex, push_json) +} + +/// Decrypt a legacy (v0) envelope from its four fields. +#[uniffi::export] +pub fn wake_decrypt_v0( + secret_key_hex: String, + cipher: String, + iv: String, + tag: String, + public_key: String, +) -> Result { + crate::modules::wake::wake_decrypt_v0(secret_key_hex, cipher, iv, tag, public_key) +} + +/// Decrypt a v1 container given as JSON. +#[uniffi::export] +pub fn wake_decrypt_v1( + secret_key_hex: String, + container_json: String, +) -> Result { + crate::modules::wake::wake_decrypt_v1(secret_key_hex, container_json) +} + +/// Read the gateway's audience, clock and version. +#[uniffi::export] +pub async fn wake_server_info(gateway_url: String) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { crate::modules::wake::wake_server_info(gateway_url).await }) + .await + .unwrap_or_else(|e| { + Err(WakeError::RequestFailed { + reason: format!("Runtime error: {}", e), + }) + }) +} + +/// Register the device, or update its registration, with one proof per identity. +#[uniffi::export] +pub async fn wake_register( + gateway_url: String, + request: WakeRegistrationRequest, + proofs: Vec, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { crate::modules::wake::wake_register(gateway_url, request, proofs).await }) + .await + .unwrap_or_else(|e| { + Err(WakeError::RequestFailed { + reason: format!("Runtime error: {}", e), + }) + }) +} + +/// Acknowledge a wake by the id from its envelope. +#[uniffi::export] +pub async fn wake_ack( + gateway_url: String, + device_secret: String, + wake_id: String, + outcome: WakeAckOutcome, +) -> Result<(), WakeError> { + let rt = ensure_runtime(); + rt.spawn(async move { + crate::modules::wake::wake_ack(gateway_url, device_secret, wake_id, outcome).await + }) + .await + .unwrap_or_else(|e| { + Err(WakeError::RequestFailed { + reason: format!("Runtime error: {}", e), + }) + }) +} + +/// Mark the app as in use for `ttl_secs` (clamped to 15..=300). Returns the expiry, unix seconds. +#[uniffi::export] +pub async fn wake_set_presence( + gateway_url: String, + device_secret: String, + ttl_secs: u32, +) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { + crate::modules::wake::wake_set_presence(gateway_url, device_secret, ttl_secs).await + }) + .await + .unwrap_or_else(|e| { + Err(WakeError::RequestFailed { + reason: format!("Runtime error: {}", e), + }) + }) +} + +/// End presence early. +#[uniffi::export] +pub async fn wake_clear_presence( + gateway_url: String, + device_secret: String, +) -> Result<(), WakeError> { + let rt = ensure_runtime(); + rt.spawn( + async move { crate::modules::wake::wake_clear_presence(gateway_url, device_secret).await }, + ) + .await + .unwrap_or_else(|e| { + Err(WakeError::RequestFailed { + reason: format!("Runtime error: {}", e), + }) + }) +} + +/// Replace the device's topics. Returns the stored topics; unknown names are dropped. +#[uniffi::export] +pub async fn wake_set_topics( + gateway_url: String, + device_secret: String, + topics: Vec, +) -> Result, WakeError> { + let rt = ensure_runtime(); + rt.spawn(async move { + crate::modules::wake::wake_set_topics(gateway_url, device_secret, topics).await + }) + .await + .unwrap_or_else(|e| { + Err(WakeError::RequestFailed { + reason: format!("Runtime error: {}", e), + }) + }) +} + +/// Unregister the device and revoke its secret. +#[uniffi::export] +pub async fn wake_unregister(gateway_url: String, device_secret: String) -> Result<(), WakeError> { + let rt = ensure_runtime(); + rt.spawn(async move { crate::modules::wake::wake_unregister(gateway_url, device_secret).await }) + .await + .unwrap_or_else(|e| { + Err(WakeError::RequestFailed { + reason: format!("Runtime error: {}", e), + }) + }) +} + +/// List the topics devices can subscribe to. +#[uniffi::export] +pub async fn wake_list_topics(gateway_url: String) -> Result, WakeError> { + let rt = ensure_runtime(); + rt.spawn(async move { crate::modules::wake::wake_list_topics(gateway_url).await }) + .await + .unwrap_or_else(|e| { + Err(WakeError::RequestFailed { + reason: format!("Runtime error: {}", e), + }) + }) +} + // ============================================================================ // Trezor Hardware Wallet Functions // ============================================================================ diff --git a/src/modules/mod.rs b/src/modules/mod.rs index d628203..6538620 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -11,3 +11,4 @@ pub mod seedqr; pub mod trezor; pub mod ur; pub mod usdt; +pub mod wake; diff --git a/src/modules/wake/README.md b/src/modules/wake/README.md new file mode 100644 index 0000000..1a45092 --- /dev/null +++ b/src/modules/wake/README.md @@ -0,0 +1,146 @@ +# Wake Module + +Device side of the wake push gateway: generating the device's keys and +credentials, preparing and sending a signed registration, decrypting the +wakes that arrive in pushes, and the device routes of the gateway's v1 API +(acks, presence, topics, unregister). + +> The v1 wire format is experimental until the Bitkit apps adopt it. The +> legacy (v0) envelope is the one deployed apps already decrypt, and is +> stable. + +The module is a port of the gateway's `wake-proto` crate, which bitkit-core +cannot depend on. It carries no database state: the host keeps the key pair, +the device secret and the install id. + +## Available Methods + +| Method | What it does | +|---|---| +| `wakeGenerateKeypair` | secp256k1 key pair wakes are encrypted to | +| `wakeGenerateDeviceSecret` | `wkd_` bearer secret and its SHA-256 | +| `wakeGenerateInstallId` | 22-character install id | +| `wakePrepareRegistration` | validates a registration and builds the message every identity signs | +| `wakeSignPubkyProof` | signs that message with the pubky key (only registration messages) | +| `wakeRegister` | `POST /v1/devices` with one proof per identity | +| `wakeDecryptPush` | finds and decrypts the wake in an APNs payload or FCM data map | +| `wakeDecryptV0`, `wakeDecryptV1` | decrypt one envelope directly | +| `wakeAck` | `POST /v1/acks` | +| `wakeSetPresence`, `wakeClearPresence` | `PUT` / `DELETE /v1/presence` | +| `wakeSetTopics` | `PUT /v1/devices/self/topics` | +| `wakeUnregister` | `DELETE /v1/devices/self` | +| `wakeServerInfo` | `GET /v1/info` (the audience to register with) | +| `wakeListTopics` | `GET /v1/topics` | + +Grants and peer sends are not exposed yet. + +## Registering + +Keep the encryption secret key and the device secret where the notification +extension can read them (the shared keychain on iOS). Register again whenever +the push token, the key or the topics change; a newer registration from the +same install replaces the older one. + +```swift +import BitkitCore +import LDKNode + +func registerForWakes(node: Node, installId: String, pubkySecretHex: String, pubkyZ32: String, + pushToken: String) async throws { + let gateway = "https://wake.example" + let info = try await wakeServerInfo(gatewayUrl: gateway) + let keys = try wakeGenerateKeypair() // store both, reuse across launches + let secret = try wakeGenerateDeviceSecret() // store the token + let nodeId = node.nodeId() + + let request = try wakePrepareRegistration( + audience: info.audience, + app: "bitkit", + installId: installId, // wakeGenerateInstallId() once per install + platform: .apns, + environment: .production, + pushToken: pushToken, // lowercase hex on APNs + encryptionPublicKey: keys.publicKeyHex, + secretSha256: secret.sha256Hex, + identities: ["ln:\(nodeId)", "pk:\(pubkyZ32)"], + topics: ["blocktank.*"], + timestamp: nil + ) + let proofs = [ + WakeIdentityProof(identity: "ln:\(nodeId)", + signature: node.signMessage(msg: Array(request.message.utf8))), + WakeIdentityProof(identity: "pk:\(pubkyZ32)", + signature: try wakeSignPubkyProof(secretKeyHex: pubkySecretHex, message: request.message)), + ] + let registration = try await wakeRegister(gatewayUrl: gateway, request: request, proofs: proofs) + print("registered \(registration.deviceId), unknown topics: \(registration.unknownTopics)") +} +``` + +The request must reach the gateway within 600 seconds of `timestamp`. + +## Handling a push + +```swift +let envelope = try wakeDecryptPush(secretKeyHex: keys.secretKeyHex, pushJson: payloadJson) +if envelope.fallback { + // A visible fallback alert: nothing to decrypt, the wake was not handled in time. +} else if let id = envelope.id { + // v1: handle envelope.topic / envelope.payloadJson, then acknowledge. + try await wakeAck(gatewayUrl: gateway, deviceSecret: secret.token, wakeId: id, outcome: .handled) +} else { + // v0 (legacy): no id and nothing to acknowledge. +} +``` + +`pushJson` is the APNs `userInfo` dictionary or the FCM data map, serialized +as JSON. Four shapes are recognised: the legacy APNs `aps.alert.payload`, the +legacy flat FCM data (`cipher`, `iv`, `tag`, `publicKey`), a v1 `wake` object +(APNs) and a v1 `wake` JSON string (FCM). A `wake_fallback` marker returns +`fallback = true` without reading the key. + +Acknowledging `.handled` cancels the fallback alert. `.noHandler` and +`.failed` are recorded, and the fallback still fires. + +## Crypto and wire compatibility + +- `S33 = compressed(d * P)` from `bitcoin::secp256k1::ecdh::shared_secret_point`, + i.e. `(0x02 | y & 1) || x`. +- `key = SHA256(SHA256(S33 || label))`, two plain SHA-256 passes (not the + byte-reversed `sha256d` display form). +- AES-256-GCM, no AAD, 16-byte tag. The IV must be 12 or 16 bytes. +- v0: label `bitkit-notifications`, 16-byte IV, plaintext + `{"source","type","payload","createdAt"}`; the envelope's topic is + `source.type`. +- v1: label `wake-v1`, 12-byte IV, container `{"v":1,...}`, plaintext + `{"v":1,"id","topic","urgency","deadline","payload"}`. Any other `v` is + `UnsupportedEnvelope`. +- The registration message is the gateway's 12-line preimage: identities and + topics are deduplicated and sorted bytewise, and a newline in any field, or + an empty list entry or a comma in one, is rejected. +- `payloadJson` is the producer's payload exactly as encrypted. + +Error messages never contain keys, secrets or decrypted plaintext. + +## Test vectors + +`test_vectors/*.json` are copied byte for byte from `crates/wake-proto/vectors` +of the wake repository at commit +`782f66510c505b0331e6299e96c1233d3c263484`. The tests assert them exactly: +the v0 server and client envelopes with their shared points and keys, the v1 +envelope, the registration preimage with both proofs, the legacy signature +(used to check the test signer), and every push shape in `pushes.json`. + +To update them, copy the files from a newer wake commit, change the hash +above and run `cargo test wake`. + +## Tests + +```bash +cargo test wake +WAKE_GATEWAY_URL=http://127.0.0.1:9010 cargo test wake -- --ignored # against a running gateway +``` + +The ignored test registers with an `ln:` proof (signed by a local stand-in for +ldk-node `signMessage`) and a `pk:` proof, then sets and clears presence, +lists and sets topics, and unregisters. diff --git a/src/modules/wake/client.rs b/src/modules/wake/client.rs new file mode 100644 index 0000000..a512278 --- /dev/null +++ b/src/modules/wake/client.rs @@ -0,0 +1,516 @@ +//! The device side of the gateway's v1 HTTP API. +//! +//! Every call is a pure `build_*` function that turns the arguments into an +//! [`HttpRequest`], a thin executor that sends it, and a pure `parse_*` +//! function that maps the [`HttpResponse`] to a result, so everything but +//! the network is tested without a gateway. + +use std::time::Duration; + +use reqwest::header::{ACCEPT, CONTENT_TYPE}; +use reqwest::Method; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use url::Url; + +use super::crypto::parse_wake_id; +use super::errors::{json_error, WakeError}; +use super::registration::{is_valid_device_secret, RegistrationFields}; +use super::types::{ + WakeAckOutcome, WakeIdentityProof, WakeRegistration, WakeRegistrationRequest, WakeServerInfo, + WakeTopic, +}; + +const CONNECT_TIMEOUT: Duration = Duration::from_secs(10); +const REQUEST_TIMEOUT: Duration = Duration::from_secs(15); + +/// A request to the gateway. `bearer` is a device secret, so this type has +/// no `Debug`. +pub(crate) struct HttpRequest { + pub(crate) method: Method, + pub(crate) url: Url, + pub(crate) bearer: Option, + /// JSON body. + pub(crate) body: Option, +} + +/// A response from the gateway. +pub(crate) struct HttpResponse { + pub(crate) status: u16, + pub(crate) body: String, +} + +impl HttpResponse { + fn is_success(&self) -> bool { + (200..300).contains(&self.status) + } +} + +// ----- Wire bodies ----- + +#[derive(Serialize)] +struct RegisterBody<'a> { + app: &'a str, + install_id: &'a str, + platform: &'a str, + environment: &'a str, + push_token: &'a str, + encryption_key: &'a str, + secret_sha256: &'a str, + identities: &'a [String], + topics: &'a [String], + ts: u64, + proofs: Vec>, +} + +#[derive(Serialize)] +struct ProofBody<'a> { + identity: &'a str, + sig: &'a str, +} + +#[derive(Serialize)] +struct AckBody<'a> { + id: &'a str, + outcome: &'a str, +} + +#[derive(Serialize)] +struct PresenceBody { + ttl_secs: u32, +} + +#[derive(Serialize)] +struct TopicsBody<'a> { + topics: &'a [String], +} + +#[derive(Deserialize)] +struct ErrorBody { + error: String, + message: String, +} + +#[derive(Deserialize)] +struct InfoResponse { + audience: String, + server_time: u64, + version: String, +} + +#[derive(Deserialize)] +struct RegisterResponse { + device_id: String, + identities: Vec, + topics: Vec, + unknown_topics: Vec, + server_time: u64, +} + +#[derive(Deserialize)] +struct PresenceResponse { + expires_at: u64, +} + +#[derive(Deserialize)] +struct SetTopicsResponse { + topics: Vec, +} + +#[derive(Deserialize)] +struct TopicsResponse { + topics: Vec, +} + +#[derive(Deserialize)] +struct TopicWire { + name: String, + description: String, + urgency: String, + deadline_secs: u32, + peer: bool, + alertable: bool, + display: Option, +} + +#[derive(Deserialize)] +struct DisplayWire { + title: String, + body: String, +} + +// ----- Builders ----- + +/// `gateway_url` with the path ending in `/`, so relative routes append to +/// any path prefix the gateway is mounted under. +fn base_url(gateway_url: &str) -> Result { + let mut url = Url::parse(gateway_url) + .map_err(|_| WakeError::invalid_input("gateway url is not a valid url"))?; + if !matches!(url.scheme(), "http" | "https") { + return Err(WakeError::invalid_input( + "gateway url must be http or https", + )); + } + if !url.path().ends_with('/') { + let path = format!("{}/", url.path()); + url.set_path(&path); + } + url.set_query(None); + url.set_fragment(None); + Ok(url) +} + +fn request( + method: Method, + gateway_url: &str, + route: &str, + bearer: Option<&str>, + body: Option, +) -> Result { + let url = base_url(gateway_url)? + .join(route) + .map_err(|_| WakeError::invalid_input("gateway url cannot be joined"))?; + let bearer = match bearer { + Some(token) if !is_valid_device_secret(token) => { + return Err(WakeError::invalid_input("malformed device secret")) + } + other => other.map(str::to_string), + }; + Ok(HttpRequest { + method, + url, + bearer, + body, + }) +} + +fn to_json(value: &T) -> String { + // Bodies hold only strings, integers and string lists. + serde_json::to_string(value).expect("request body serialization cannot fail") +} + +/// `GET /v1/info` +pub(crate) fn build_server_info(gateway_url: &str) -> Result { + request(Method::GET, gateway_url, "v1/info", None, None) +} + +/// `GET /v1/topics` +pub(crate) fn build_list_topics(gateway_url: &str) -> Result { + request(Method::GET, gateway_url, "v1/topics", None, None) +} + +/// `POST /v1/devices`. The fields must still be the ones `message` was +/// built from, or no proof could verify. +pub(crate) fn build_register( + gateway_url: &str, + registration: &WakeRegistrationRequest, + proofs: &[WakeIdentityProof], +) -> Result { + let signed = RegistrationFields { + audience: ®istration.audience, + app: ®istration.app, + install_id: ®istration.install_id, + platform: registration.platform, + environment: registration.environment, + push_token: ®istration.push_token, + encryption_key: ®istration.encryption_public_key, + secret_sha256: ®istration.secret_sha256, + identities: ®istration.identities, + topics: ®istration.topics, + ts: registration.timestamp, + } + .preimage()?; + if signed != registration.message { + return Err(WakeError::invalid_input( + "the registration changed after its message was prepared", + )); + } + let body = RegisterBody { + app: ®istration.app, + install_id: ®istration.install_id, + platform: registration.platform.as_str(), + environment: registration.environment.as_str(), + push_token: ®istration.push_token, + encryption_key: ®istration.encryption_public_key, + secret_sha256: ®istration.secret_sha256, + identities: ®istration.identities, + topics: ®istration.topics, + ts: registration.timestamp, + proofs: proofs + .iter() + .map(|proof| ProofBody { + identity: &proof.identity, + sig: &proof.signature, + }) + .collect(), + }; + request( + Method::POST, + gateway_url, + "v1/devices", + None, + Some(to_json(&body)), + ) +} + +/// `POST /v1/acks` +pub(crate) fn build_ack( + gateway_url: &str, + device_secret: &str, + wake_id: &str, + outcome: WakeAckOutcome, +) -> Result { + let id = parse_wake_id(wake_id)?; + let body = AckBody { + id: &id, + outcome: outcome.as_str(), + }; + request( + Method::POST, + gateway_url, + "v1/acks", + Some(device_secret), + Some(to_json(&body)), + ) +} + +/// `PUT /v1/presence`. The gateway clamps `ttl_secs` to 15..=300. +pub(crate) fn build_set_presence( + gateway_url: &str, + device_secret: &str, + ttl_secs: u32, +) -> Result { + request( + Method::PUT, + gateway_url, + "v1/presence", + Some(device_secret), + Some(to_json(&PresenceBody { ttl_secs })), + ) +} + +/// `DELETE /v1/presence` +pub(crate) fn build_clear_presence( + gateway_url: &str, + device_secret: &str, +) -> Result { + request( + Method::DELETE, + gateway_url, + "v1/presence", + Some(device_secret), + None, + ) +} + +/// `PUT /v1/devices/self/topics` +pub(crate) fn build_set_topics( + gateway_url: &str, + device_secret: &str, + topics: &[String], +) -> Result { + request( + Method::PUT, + gateway_url, + "v1/devices/self/topics", + Some(device_secret), + Some(to_json(&TopicsBody { topics })), + ) +} + +/// `DELETE /v1/devices/self` +pub(crate) fn build_unregister( + gateway_url: &str, + device_secret: &str, +) -> Result { + request( + Method::DELETE, + gateway_url, + "v1/devices/self", + Some(device_secret), + None, + ) +} + +// ----- Parsers ----- + +/// The error of a non-2xx response: the gateway's +/// `{"error":"","message":"..."}`, or code `unknown` for any other +/// body (a proxy error page, say). +fn rejection(response: &HttpResponse) -> WakeError { + match serde_json::from_str::(&response.body) { + Ok(body) => WakeError::GatewayRejected { + status: response.status, + code: body.error, + detail: body.message, + }, + Err(_) => WakeError::GatewayRejected { + status: response.status, + code: "unknown".to_string(), + detail: "the response carries no wake error body".to_string(), + }, + } +} + +fn success_json(response: &HttpResponse) -> Result { + if !response.is_success() { + return Err(rejection(response)); + } + serde_json::from_str(&response.body).map_err(|e| WakeError::invalid_response(json_error(&e))) +} + +/// For routes that answer 204. +pub(crate) fn parse_no_content(response: &HttpResponse) -> Result<(), WakeError> { + if response.is_success() { + Ok(()) + } else { + Err(rejection(response)) + } +} + +pub(crate) fn parse_server_info(response: &HttpResponse) -> Result { + let info: InfoResponse = success_json(response)?; + Ok(WakeServerInfo { + audience: info.audience, + server_time: info.server_time, + version: info.version, + }) +} + +pub(crate) fn parse_list_topics(response: &HttpResponse) -> Result, WakeError> { + let topics: TopicsResponse = success_json(response)?; + Ok(topics + .topics + .into_iter() + .map(|topic| { + let (title, body) = match topic.display { + Some(display) => (Some(display.title), Some(display.body)), + None => (None, None), + }; + WakeTopic { + name: topic.name, + description: topic.description, + urgency: topic.urgency, + deadline_secs: topic.deadline_secs, + alertable: topic.alertable, + title, + body, + peer: topic.peer, + } + }) + .collect()) +} + +pub(crate) fn parse_register(response: &HttpResponse) -> Result { + let registered: RegisterResponse = success_json(response)?; + Ok(WakeRegistration { + device_id: registered.device_id, + identities: registered.identities, + topics: registered.topics, + unknown_topics: registered.unknown_topics, + server_time: registered.server_time, + }) +} + +/// The presence expiry, unix seconds. +pub(crate) fn parse_set_presence(response: &HttpResponse) -> Result { + let presence: PresenceResponse = success_json(response)?; + Ok(presence.expires_at) +} + +/// The stored topics; unknown exact names are dropped by the gateway. +pub(crate) fn parse_set_topics(response: &HttpResponse) -> Result, WakeError> { + let topics: SetTopicsResponse = success_json(response)?; + Ok(topics.topics) +} + +// ----- Executor ----- + +async fn send(request: HttpRequest) -> Result { + let failed = |e: reqwest::Error| WakeError::RequestFailed { + reason: e.to_string(), + }; + // A redirect would turn a POST into a GET; surface it as an error instead. + let client = reqwest::Client::builder() + .connect_timeout(CONNECT_TIMEOUT) + .timeout(REQUEST_TIMEOUT) + .redirect(reqwest::redirect::Policy::none()) + .build() + .map_err(failed)?; + let mut builder = client + .request(request.method, request.url) + .header(ACCEPT, "application/json"); + if let Some(token) = request.bearer { + builder = builder.bearer_auth(token); + } + if let Some(body) = request.body { + builder = builder.header(CONTENT_TYPE, "application/json").body(body); + } + let response = builder.send().await.map_err(failed)?; + let status = response.status().as_u16(); + let body = response.text().await.map_err(failed)?; + Ok(HttpResponse { status, body }) +} + +/// Reads the gateway's audience and clock. +pub async fn wake_server_info(gateway_url: String) -> Result { + parse_server_info(&send(build_server_info(&gateway_url)?).await?) +} + +/// Registers the device, or updates its registration. +pub async fn wake_register( + gateway_url: String, + request: WakeRegistrationRequest, + proofs: Vec, +) -> Result { + parse_register(&send(build_register(&gateway_url, &request, &proofs)?).await?) +} + +/// Acknowledges a wake by the id from its envelope. +pub async fn wake_ack( + gateway_url: String, + device_secret: String, + wake_id: String, + outcome: WakeAckOutcome, +) -> Result<(), WakeError> { + let request = build_ack(&gateway_url, &device_secret, &wake_id, outcome)?; + parse_no_content(&send(request).await?) +} + +/// Marks the app as in use for `ttl_secs` and returns when that expires. +pub async fn wake_set_presence( + gateway_url: String, + device_secret: String, + ttl_secs: u32, +) -> Result { + let request = build_set_presence(&gateway_url, &device_secret, ttl_secs)?; + parse_set_presence(&send(request).await?) +} + +/// Ends presence early. +pub async fn wake_clear_presence( + gateway_url: String, + device_secret: String, +) -> Result<(), WakeError> { + parse_no_content(&send(build_clear_presence(&gateway_url, &device_secret)?).await?) +} + +/// Replaces the device's topics and returns the stored ones. +pub async fn wake_set_topics( + gateway_url: String, + device_secret: String, + topics: Vec, +) -> Result, WakeError> { + let request = build_set_topics(&gateway_url, &device_secret, &topics)?; + parse_set_topics(&send(request).await?) +} + +/// Unregisters the device and revokes its secret. +pub async fn wake_unregister(gateway_url: String, device_secret: String) -> Result<(), WakeError> { + parse_no_content(&send(build_unregister(&gateway_url, &device_secret)?).await?) +} + +/// Lists the topics devices can subscribe to. +pub async fn wake_list_topics(gateway_url: String) -> Result, WakeError> { + parse_list_topics(&send(build_list_topics(&gateway_url)?).await?) +} diff --git a/src/modules/wake/crypto.rs b/src/modules/wake/crypto.rs new file mode 100644 index 0000000..5585a0a --- /dev/null +++ b/src/modules/wake/crypto.rs @@ -0,0 +1,424 @@ +//! Envelope decryption, ported from wake-proto `crypto`. +//! +//! The gateway encrypts every attempt with a fresh ephemeral key and IV: +//! - `S33 = compressed(d_eph * P_device)`, i.e. `(0x02 | y&1) || x`; +//! - `key = SHA256(SHA256(S33 || label))`; +//! - AES-256-GCM with no AAD and a 16-byte tag. +//! +//! v0 (the legacy bitkit-notification-server envelope) uses the label +//! `bitkit-notifications` and a 16-byte IV; v1 uses `wake-v1` and a 12-byte IV. + +use aes_gcm::aead::consts::U16; +use aes_gcm::aead::generic_array::GenericArray; +use aes_gcm::aead::{AeadInPlace, KeyInit}; +use aes_gcm::aes::Aes256; +use aes_gcm::{Aes256Gcm, AesGcm}; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use bitcoin::hashes::{sha256, Hash}; +use bitcoin::secp256k1::{ecdh, PublicKey, Secp256k1, SecretKey}; +use rand::rngs::OsRng; +use rand::RngCore; +use serde::Deserialize; +use serde_json::value::RawValue; + +use super::errors::{json_error, WakeError}; +use super::types::{WakeEnvelope, WakeKeyPair}; + +const TAG_LEN: usize = 16; + +/// Draws after which key generation reports the random source as degenerate. +/// A working generator's draw is out of range with probability about 2^-128. +const MAX_REJECTED_DRAWS: usize = 64; + +/// AES-256-GCM with a 16-byte nonce, as the legacy server used. +type Aes256Gcm16 = AesGcm; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Scheme { + V0, + V1, +} + +impl Scheme { + pub(crate) const fn label(self) -> &'static str { + match self { + Scheme::V0 => "bitkit-notifications", + Scheme::V1 => "wake-v1", + } + } +} + +/// An encrypted envelope: `cipher` is padded standard base64 of the +/// ciphertext, `iv`, `tag` and the ephemeral `publicKey` are hex. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct Container { + scheme: Scheme, + cipher: Vec, + iv: Vec, + tag: [u8; TAG_LEN], + public_key: [u8; 33], +} + +impl Container { + /// Builds a container from its wire strings. Hex may be upper or lower + /// case; the IV must be 12 or 16 bytes whatever the scheme, because that + /// is what the apps accept. + pub(crate) fn from_fields( + scheme: Scheme, + cipher: &str, + iv: &str, + tag: &str, + public_key: &str, + ) -> Result { + let cipher = STANDARD + .decode(cipher) + .map_err(|_| WakeError::invalid_input("cipher is not padded standard base64"))?; + let iv = hex::decode(iv).map_err(|_| WakeError::invalid_input("iv is not hex"))?; + let tag = hex::decode(tag) + .ok() + .and_then(|t| <[u8; TAG_LEN]>::try_from(t).ok()) + .ok_or_else(|| WakeError::invalid_input("tag must be 16 hex bytes"))?; + let public_key = hex::decode(public_key) + .ok() + .and_then(|k| <[u8; 33]>::try_from(k).ok()) + .ok_or_else(|| WakeError::invalid_input("publicKey must be 33 hex bytes"))?; + if iv.len() != 12 && iv.len() != 16 { + return Err(WakeError::invalid_input("iv must be 12 or 16 bytes")); + } + Ok(Container { + scheme, + cipher, + iv, + tag, + public_key, + }) + } + + /// Parses container JSON. A missing `v` means v0, `"v":1` means v1, and + /// any other `v` (`null` included) is unsupported. Unknown keys are + /// ignored, as the apps ignore them. + pub(crate) fn from_json(json: &str) -> Result { + let wire: ContainerWire = serde_json::from_str(json) + .map_err(|_| WakeError::invalid_input("malformed container json"))?; + let scheme = match wire.v { + None => Scheme::V0, + Some(v) if v.as_u64() == Some(1) => Scheme::V1, + Some(_) => return Err(WakeError::unsupported("unsupported envelope version")), + }; + Container::from_fields(scheme, &wire.cipher, &wire.iv, &wire.tag, &wire.public_key) + } + + pub(crate) fn scheme(&self) -> Scheme { + self.scheme + } + + /// Decrypts with the device's secret key and returns the plaintext bytes. + pub(crate) fn decrypt(&self, device_secret: &SecretKey) -> Result, WakeError> { + let point = shared_point(device_secret, &self.public_key)?; + let key = derive_key(&point, self.scheme.label()); + open(&key, &self.iv, &self.cipher, &self.tag) + } + + /// Decrypts and parses the plaintext of this container's scheme. + pub(crate) fn open_envelope( + &self, + device_secret: &SecretKey, + ) -> Result { + let plaintext = self.decrypt(device_secret)?; + match self.scheme { + Scheme::V0 => legacy_envelope(&plaintext), + Scheme::V1 => v1_envelope(&plaintext), + } + } +} + +#[derive(Deserialize)] +struct ContainerWire { + /// `None` only when the key is absent; `"v":null` is `Some(Null)`. + #[serde(default, deserialize_with = "present")] + v: Option, + cipher: String, + iv: String, + tag: String, + #[serde(rename = "publicKey")] + public_key: String, +} + +fn present<'de, D: serde::Deserializer<'de>>( + deserializer: D, +) -> Result, D::Error> { + serde_json::Value::deserialize(deserializer).map(Some) +} + +/// `S33 = compressed(secret * public)`, the shared point both sides hash +/// into the AES key. +pub(crate) fn shared_point(secret: &SecretKey, public: &[u8; 33]) -> Result<[u8; 33], WakeError> { + if public[0] != 0x02 && public[0] != 0x03 { + return Err(WakeError::invalid_key("public key must be compressed")); + } + let point = PublicKey::from_slice(public) + .map_err(|_| WakeError::invalid_key("public key is not on the curve"))?; + let xy = ecdh::shared_secret_point(&point, secret); + let mut out = [0u8; 33]; + out[0] = 0x02 | (xy[63] & 1); + out[1..].copy_from_slice(&xy[..32]); + Ok(out) +} + +/// `SHA256(SHA256(S33 || label))`, the AES-256 key of both schemes. +/// +/// Two plain SHA-256 passes. Not the `sha256d` hash type, whose hex display +/// is byte-reversed. +pub(crate) fn derive_key(shared_point: &[u8; 33], label: &str) -> [u8; 32] { + let mut input = Vec::with_capacity(shared_point.len() + label.len()); + input.extend_from_slice(shared_point); + input.extend_from_slice(label.as_bytes()); + let first = sha256::Hash::hash(&input); + sha256::Hash::hash(first.as_byte_array()).to_byte_array() +} + +fn open( + key: &[u8; 32], + iv: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], +) -> Result, WakeError> { + let mut buffer = ciphertext.to_vec(); + let tag = GenericArray::from_slice(tag); + match iv.len() { + 12 => Aes256Gcm::new(GenericArray::from_slice(key)).decrypt_in_place_detached( + GenericArray::from_slice(iv), + b"", + &mut buffer, + tag, + ), + 16 => Aes256Gcm16::new(GenericArray::from_slice(key)).decrypt_in_place_detached( + GenericArray::from_slice(iv), + b"", + &mut buffer, + tag, + ), + _ => return Err(WakeError::invalid_input("iv must be 12 or 16 bytes")), + } + .map_err(|_| WakeError::DecryptionFailed { + reason: "authentication failed".to_string(), + })?; + Ok(buffer) +} + +/// The legacy plaintext: +/// `{"source":S,"type":T,"payload":,"createdAt":"..."}`. +#[derive(Deserialize)] +struct LegacyPlaintext { + source: String, + #[serde(rename = "type")] + kind: String, + #[serde(default)] + payload: Option>, + #[serde(rename = "createdAt")] + created_at: String, +} + +/// The v1 plaintext: +/// `{"v":1,"id":"","topic":T,"urgency":U,"deadline":N,"payload":}`. +#[derive(Deserialize)] +struct V1Plaintext { + v: u64, + id: String, + topic: String, + urgency: Urgency, + deadline: u64, + #[serde(default)] + payload: Option>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +enum Urgency { + Background, + Alert, + TimeSensitive, +} + +impl Urgency { + const fn as_str(&self) -> &'static str { + match self { + Urgency::Background => "background", + Urgency::Alert => "alert", + Urgency::TimeSensitive => "time_sensitive", + } + } +} + +fn malformed_plaintext(e: serde_json::Error) -> WakeError { + WakeError::invalid_input(format!("malformed plaintext, {}", json_error(&e))) +} + +fn legacy_envelope(plaintext: &[u8]) -> Result { + let legacy: LegacyPlaintext = serde_json::from_slice(plaintext).map_err(malformed_plaintext)?; + Ok(WakeEnvelope { + version: 0, + id: None, + topic: format!("{}.{}", legacy.source, legacy.kind), + urgency: None, + deadline: None, + payload_json: legacy.payload.map(|p| p.get().to_string()), + created_at: Some(legacy.created_at), + fallback: false, + }) +} + +fn v1_envelope(plaintext: &[u8]) -> Result { + let wire: V1Plaintext = serde_json::from_slice(plaintext).map_err(malformed_plaintext)?; + let id = parse_wake_id(&wire.id)?; + if wire.v != 1 { + return Err(WakeError::unsupported("unsupported plaintext version")); + } + Ok(WakeEnvelope { + version: 1, + id: Some(id), + topic: wire.topic, + urgency: Some(wire.urgency.as_str().to_string()), + deadline: Some(wire.deadline), + payload_json: wire.payload.map(|p| p.get().to_string()), + created_at: None, + fallback: false, + }) +} + +/// A wake id in its canonical hyphenated form. +pub(crate) fn parse_wake_id(id: &str) -> Result { + uuid::Uuid::parse_str(id) + .map(|id| id.to_string()) + .map_err(|_| WakeError::invalid_input("wake id is not a uuid")) +} + +/// Parses a 32-byte secp256k1 secret key written as hex. +pub(crate) fn parse_secret_key(secret_key_hex: &str) -> Result { + let bytes = parse_hex_32(secret_key_hex) + .ok_or_else(|| WakeError::invalid_key("secret key must be 32 hex bytes"))?; + SecretKey::from_slice(&bytes).map_err(|_| WakeError::invalid_key("secret key out of range")) +} + +pub(crate) fn parse_hex_32(s: &str) -> Option<[u8; 32]> { + hex::decode(s).ok()?.try_into().ok() +} + +/// Fills `dest` from the operating system's random number generator. +pub(crate) fn os_random(dest: &mut [u8]) -> Result<(), WakeError> { + OsRng + .try_fill_bytes(dest) + .map_err(|_| WakeError::invalid_key("the system random number generator failed")) +} + +/// Draws secret keys from `fill` until one is in range, at most +/// `MAX_REJECTED_DRAWS` times, so a broken source fails instead of looping. +pub(crate) fn generate_keypair_with( + mut fill: impl FnMut(&mut [u8]) -> Result<(), WakeError>, +) -> Result { + for _ in 0..MAX_REJECTED_DRAWS { + let mut candidate = [0u8; 32]; + fill(&mut candidate)?; + if let Ok(secret) = SecretKey::from_slice(&candidate) { + let public = PublicKey::from_secret_key(&Secp256k1::signing_only(), &secret); + return Ok(WakeKeyPair { + secret_key_hex: hex::encode(secret.secret_bytes()), + public_key_hex: hex::encode(public.serialize()), + }); + } + } + Err(WakeError::invalid_key("entropy source is degenerate")) +} + +/// Generates the device's push encryption key pair. +pub fn wake_generate_keypair() -> Result { + generate_keypair_with(os_random) +} + +/// Decrypts a legacy (v0) envelope given its four wire fields. +pub fn wake_decrypt_v0( + secret_key_hex: String, + cipher: String, + iv: String, + tag: String, + public_key: String, +) -> Result { + let secret = parse_secret_key(&secret_key_hex)?; + Container::from_fields(Scheme::V0, &cipher, &iv, &tag, &public_key)?.open_envelope(&secret) +} + +/// Decrypts a v1 container given as JSON. +pub fn wake_decrypt_v1( + secret_key_hex: String, + container_json: String, +) -> Result { + let secret = parse_secret_key(&secret_key_hex)?; + let container = Container::from_json(&container_json)?; + if container.scheme() != Scheme::V1 { + return Err(WakeError::unsupported("expected a v1 container")); + } + container.open_envelope(&secret) +} + +/// Encrypts like the gateway, with an explicit ephemeral key and IV, so tests +/// can rebuild the golden envelopes. Reusing a (key, IV) pair breaks GCM. +#[cfg(test)] +pub(crate) fn seal_deterministic( + scheme: Scheme, + device_public: &[u8; 33], + plaintext: &[u8], + ephemeral: &SecretKey, + iv: &[u8], +) -> Container { + let point = shared_point(ephemeral, device_public).unwrap(); + let key = derive_key(&point, scheme.label()); + let mut buffer = plaintext.to_vec(); + let tag = match iv.len() { + 12 => Aes256Gcm::new(GenericArray::from_slice(&key)).encrypt_in_place_detached( + GenericArray::from_slice(iv), + b"", + &mut buffer, + ), + 16 => Aes256Gcm16::new(GenericArray::from_slice(&key)).encrypt_in_place_detached( + GenericArray::from_slice(iv), + b"", + &mut buffer, + ), + other => panic!("iv of {other} bytes"), + } + .unwrap(); + Container { + scheme, + cipher: buffer, + iv: iv.to_vec(), + tag: tag.into(), + public_key: PublicKey::from_secret_key(&Secp256k1::signing_only(), ephemeral).serialize(), + } +} + +#[cfg(test)] +impl Container { + /// Compact container JSON in the wire key order. + pub(crate) fn to_json(&self) -> String { + let version = match self.scheme { + Scheme::V0 => "", + Scheme::V1 => "\"v\":1,", + }; + format!( + "{{{version}\"cipher\":\"{}\",\"iv\":\"{}\",\"tag\":\"{}\",\"publicKey\":\"{}\"}}", + STANDARD.encode(&self.cipher), + hex::encode(&self.iv), + hex::encode(self.tag), + hex::encode(self.public_key) + ) + } + + pub(crate) fn tag_mut(&mut self) -> &mut [u8; TAG_LEN] { + &mut self.tag + } + + pub(crate) fn public_key_mut(&mut self) -> &mut [u8; 33] { + &mut self.public_key + } +} diff --git a/src/modules/wake/errors.rs b/src/modules/wake/errors.rs new file mode 100644 index 0000000..4bdde0e --- /dev/null +++ b/src/modules/wake/errors.rs @@ -0,0 +1,73 @@ +/// Errors of the wake module. Messages never contain secrets, push tokens or +/// decrypted plaintext. +#[derive(uniffi::Error, thiserror::Error, Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum WakeError { + #[error("Invalid input: {reason}")] + InvalidInput { reason: String }, + + #[error("Invalid key: {reason}")] + InvalidKey { reason: String }, + + #[error("Decryption failed: {reason}")] + DecryptionFailed { reason: String }, + + #[error("Unsupported envelope: {reason}")] + UnsupportedEnvelope { reason: String }, + + #[error("Request failed: {reason}")] + RequestFailed { reason: String }, + + #[error("Gateway rejected the request with HTTP {status} ({code}): {detail}")] + GatewayRejected { + status: u16, + code: String, + detail: String, + }, + + #[error("Invalid response: {reason}")] + InvalidResponse { reason: String }, +} + +impl WakeError { + pub(crate) fn invalid_input(reason: impl Into) -> Self { + WakeError::InvalidInput { + reason: reason.into(), + } + } + + pub(crate) fn invalid_key(reason: impl Into) -> Self { + WakeError::InvalidKey { + reason: reason.into(), + } + } + + pub(crate) fn unsupported(reason: impl Into) -> Self { + WakeError::UnsupportedEnvelope { + reason: reason.into(), + } + } + + pub(crate) fn invalid_response(reason: impl Into) -> Self { + WakeError::InvalidResponse { + reason: reason.into(), + } + } +} + +/// The category and position of a JSON error, without its message: serde_json +/// messages quote the offending value, which may be decrypted plaintext. +pub(crate) fn json_error(e: &serde_json::Error) -> String { + use serde_json::error::Category; + let kind = match e.classify() { + Category::Io => "io error", + Category::Syntax => "syntax error", + Category::Data => "unexpected type or value", + Category::Eof => "unexpected end of input", + }; + format!( + "invalid json: {kind} at line {} column {}", + e.line(), + e.column() + ) +} diff --git a/src/modules/wake/mod.rs b/src/modules/wake/mod.rs new file mode 100644 index 0000000..ef8922a --- /dev/null +++ b/src/modules/wake/mod.rs @@ -0,0 +1,15 @@ +mod client; +mod crypto; +mod errors; +mod push; +mod registration; +#[cfg(test)] +mod tests; +mod types; + +pub use client::*; +pub use crypto::*; +pub use errors::*; +pub use push::*; +pub use registration::*; +pub use types::*; diff --git a/src/modules/wake/push.rs b/src/modules/wake/push.rs new file mode 100644 index 0000000..19bb64e --- /dev/null +++ b/src/modules/wake/push.rs @@ -0,0 +1,145 @@ +//! Finds the wake in a delivered push, ported from wake-proto +//! `push::parse_delivered`. + +use serde::Deserialize; +use serde_json::{Map, Value}; + +use super::crypto::{parse_secret_key, parse_wake_id, Container, Scheme}; +use super::errors::{json_error, WakeError}; +use super::types::WakeEnvelope; + +/// A wake as the device receives it. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum Delivered { + /// A legacy envelope: APNs `aps.alert.payload`, or the flat FCM data + /// fields `cipher`, `iv`, `tag` and `publicKey`. + V0(Container), + /// A v1 envelope: the APNs `wake` object or the FCM `wake` JSON string. + V1(Container), + /// A fallback alert's `wake_fallback` marker; there is nothing to decrypt. + Fallback { id: String, topic: String }, +} + +/// Finds the wake in a push. +/// +/// `push` is the JSON the app receives: +/// - APNs: the notification dictionary, carrying `aps.alert.payload` (v0), +/// a `wake` object (v1) or a `wake_fallback` object; +/// - FCM: the data map, carrying flat `cipher`, `iv`, `tag` and `publicKey` +/// strings (v0), or `wake` or `wake_fallback` as JSON strings. +/// +/// A whole FCM `messages:send` body is accepted too: the map read is +/// `message.android.data` when present, else `message.data`. +pub(crate) fn parse_delivered(push: &str) -> Result { + let root: Value = + serde_json::from_str(push).map_err(|e| WakeError::invalid_input(json_error(&e)))?; + let mut map = root + .as_object() + .ok_or_else(|| WakeError::invalid_input("push is not a JSON object"))?; + // The legacy FCM data map has a string `message` (the alert text), so + // only an object marks a `messages:send` body. + if let Some(message) = map.get("message").filter(|m| m.is_object()) { + map = message + .pointer("/android/data") + .or_else(|| message.get("data")) + .and_then(Value::as_object) + .ok_or_else(|| WakeError::unsupported("FCM message carries no data"))?; + } + if let Some(marker) = map.get("wake_fallback") { + return parse_fallback(marker); + } + if let Some(wake) = map.get("wake") { + let container = match wake { + Value::String(text) => Container::from_json(text)?, + Value::Object(_) => Container::from_json(&wake.to_string())?, + _ => return Err(WakeError::invalid_input("wake is not a container")), + }; + expect_scheme(&container, Scheme::V1)?; + return Ok(Delivered::V1(container)); + } + if let Some(payload) = map.get("aps").and_then(|aps| aps.pointer("/alert/payload")) { + if !payload.is_object() { + return Err(WakeError::invalid_input( + "aps.alert.payload is not a container", + )); + } + let container = Container::from_json(&payload.to_string())?; + expect_scheme(&container, Scheme::V0)?; + return Ok(Delivered::V0(container)); + } + if map.contains_key("cipher") { + return legacy_fields(map).map(Delivered::V0); + } + Err(WakeError::unsupported("push carries no wake")) +} + +fn parse_fallback(marker: &Value) -> Result { + #[derive(Deserialize)] + struct Marker { + v: Value, + id: String, + topic: String, + } + let marker: Marker = match marker { + Value::String(text) => serde_json::from_str(text), + other => Marker::deserialize(other), + } + .map_err(|e| WakeError::invalid_input(json_error(&e)))?; + let id = parse_wake_id(&marker.id)?; + if marker.v.as_u64() != Some(1) { + return Err(WakeError::unsupported("unsupported envelope version")); + } + Ok(Delivered::Fallback { + id, + topic: marker.topic, + }) +} + +fn legacy_fields(map: &Map) -> Result { + let field = |key: &str| { + map.get(key).and_then(Value::as_str).ok_or_else(|| { + WakeError::invalid_input("legacy data needs cipher, iv, tag and publicKey strings") + }) + }; + Container::from_fields( + Scheme::V0, + field("cipher")?, + field("iv")?, + field("tag")?, + field("publicKey")?, + ) +} + +fn expect_scheme(container: &Container, scheme: Scheme) -> Result<(), WakeError> { + if container.scheme() == scheme { + Ok(()) + } else { + Err(WakeError::unsupported( + "container version does not match where it was found", + )) + } +} + +/// Finds and decrypts the wake in a delivered push. A fallback alert returns +/// `fallback = true` without decrypting, so the key is only read for +/// envelopes. +pub fn wake_decrypt_push( + secret_key_hex: String, + push_json: String, +) -> Result { + match parse_delivered(&push_json)? { + Delivered::V0(container) | Delivered::V1(container) => { + container.open_envelope(&parse_secret_key(&secret_key_hex)?) + } + Delivered::Fallback { id, topic } => Ok(WakeEnvelope { + version: 1, + id: Some(id), + topic, + urgency: None, + deadline: None, + payload_json: None, + created_at: None, + fallback: true, + }), + } +} diff --git a/src/modules/wake/registration.rs b/src/modules/wake/registration.rs new file mode 100644 index 0000000..adb3c68 --- /dev/null +++ b/src/modules/wake/registration.rs @@ -0,0 +1,364 @@ +//! Registration preimage and credentials, ported from wake-proto +//! `registration`, `tokens` and `api`. +//! +//! Every identity signs the same 12-line UTF-8 preimage, lines joined by a +//! single `\n` with no trailing newline: +//! +//! ```text +//! wake-register-v1 +//! aud: +//! app: +//! install: +//! platform: +//! environment: +//! push_token: +//! encryption_key:<66 hex> +//! secret_sha256:<64 hex> +//! identities: +//! topics: +//! ts: +//! ``` + +use std::time::{SystemTime, UNIX_EPOCH}; + +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use base64::Engine; +use bitcoin::hashes::{sha256, Hash}; +use bitcoin::secp256k1::PublicKey; +use rand::rngs::OsRng; +use rand::RngCore; + +use super::crypto::{os_random, parse_hex_32}; +use super::errors::WakeError; +use super::types::{WakeDeviceSecret, WakeEnvironment, WakePlatform, WakeRegistrationRequest}; + +/// First line of the preimage, and its domain separator. +pub(crate) const PREIMAGE_TAG: &str = "wake-register-v1"; + +const DEVICE_SECRET_PREFIX: &str = "wkd_"; +const MAX_TOPICS: usize = 32; +const MAX_TOPIC_LEN: usize = 64; + +/// The fields the registration preimage covers. +pub(crate) struct RegistrationFields<'a> { + pub(crate) audience: &'a str, + pub(crate) app: &'a str, + pub(crate) install_id: &'a str, + pub(crate) platform: WakePlatform, + pub(crate) environment: WakeEnvironment, + pub(crate) push_token: &'a str, + pub(crate) encryption_key: &'a str, + pub(crate) secret_sha256: &'a str, + pub(crate) identities: &'a [String], + pub(crate) topics: &'a [String], + pub(crate) ts: u64, +} + +impl RegistrationFields<'_> { + /// Builds the preimage. Fails if any field contains a newline, or any + /// identity or topic is empty or contains a comma, since each would make + /// the preimage ambiguous (`[""]` and `[]` both give `topics:`). + pub(crate) fn preimage(&self) -> Result { + let scalars = [ + self.audience, + self.app, + self.install_id, + self.push_token, + self.encryption_key, + self.secret_sha256, + ]; + if scalars.iter().any(|f| f.contains('\n')) { + return Err(WakeError::invalid_input( + "a registration field contains a newline", + )); + } + let identities = sorted_list(self.identities)?; + let topics = sorted_list(self.topics)?; + Ok(format!( + "{PREIMAGE_TAG}\naud:{}\napp:{}\ninstall:{}\nplatform:{}\nenvironment:{}\n\ + push_token:{}\nencryption_key:{}\nsecret_sha256:{}\nidentities:{identities}\n\ + topics:{topics}\nts:{}", + self.audience, + self.app, + self.install_id, + self.platform.as_str(), + self.environment.as_str(), + self.push_token, + self.encryption_key, + self.secret_sha256, + self.ts, + )) + } +} + +fn sorted_list(items: &[String]) -> Result { + if items + .iter() + .any(|i| i.is_empty() || i.contains(',') || i.contains('\n')) + { + return Err(WakeError::invalid_input( + "a list entry is empty or contains a comma or newline", + )); + } + let mut sorted: Vec<&str> = items.iter().map(String::as_str).collect(); + sorted.sort_unstable(); + sorted.dedup(); + Ok(sorted.join(",")) +} + +/// Checks every field the way the gateway does, so a request it would reject +/// as `invalid_request` fails here, before anything is signed. +fn validate(fields: &RegistrationFields<'_>) -> Result<(), WakeError> { + if fields.audience.is_empty() || fields.app.is_empty() { + return Err(WakeError::invalid_input( + "audience and app must not be empty", + )); + } + if !is_valid_install_id(fields.install_id) { + return Err(WakeError::invalid_input( + "install_id must be 16 to 64 characters of [A-Za-z0-9_-]", + )); + } + if fields.platform == WakePlatform::Fcm && fields.environment != WakeEnvironment::Production { + return Err(WakeError::invalid_input( + "fcm devices are always production", + )); + } + if !is_valid_push_token(fields.platform, fields.push_token) { + return Err(WakeError::invalid_input(match fields.platform { + WakePlatform::Apns => "an apns push token is 64 to 200 lowercase hex characters", + WakePlatform::Fcm => "an fcm push token is 100 to 4096 characters of [0-9A-Za-z_:-]", + })); + } + parse_public_key_hex(fields.encryption_key)?; + if !is_lower_hex(fields.secret_sha256, 32) { + return Err(WakeError::invalid_input( + "secret_sha256 must be 64 lowercase hex characters", + )); + } + let schemes = fields + .identities + .iter() + .map(|identity| identity_scheme(identity)) + .collect::, _>>()?; + if !(1..=2).contains(&schemes.len()) || (schemes.len() == 2 && schemes[0] == schemes[1]) { + return Err(WakeError::invalid_input( + "identities must be one ln: and/or one pk: identity", + )); + } + if fields.topics.len() > MAX_TOPICS { + return Err(WakeError::invalid_input("at most 32 topics")); + } + if let Some(topic) = fields.topics.iter().find(|t| !is_valid_topic_filter(t)) { + return Err(WakeError::invalid_input(format!( + "{topic:?} is not a topic name or a .* pattern" + ))); + } + Ok(()) +} + +/// `^[A-Za-z0-9_-]{16,64}$` +fn is_valid_install_id(s: &str) -> bool { + (16..=64).contains(&s.len()) + && s.bytes() + .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-') +} + +/// APNs: even-length lowercase hex, 64 to 200 characters. FCM: +/// `^[0-9A-Za-z_:-]{100,4096}$`. +fn is_valid_push_token(platform: WakePlatform, token: &str) -> bool { + match platform { + WakePlatform::Apns => { + // `is_lower_hex` needs exactly twice `len / 2` characters, so odd + // lengths fail it. + (64..=200).contains(&token.len()) && is_lower_hex(token, token.len() / 2) + } + WakePlatform::Fcm => { + (100..=4096).contains(&token.len()) + && token + .bytes() + .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b':' | b'-')) + } + } +} + +/// Exactly `bytes` bytes written as lowercase hex. +fn is_lower_hex(s: &str, bytes: usize) -> bool { + s.len() == bytes * 2 + && s.bytes() + .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) +} + +/// A compressed key written as 66 lowercase hex characters that lies on the +/// curve. +fn parse_public_key_hex(s: &str) -> Result { + if !is_lower_hex(s, 33) || !(s.starts_with("02") || s.starts_with("03")) { + return Err(WakeError::invalid_key( + "a public key is 66 lowercase hex characters starting with 02 or 03", + )); + } + let bytes = hex::decode(s).map_err(|_| WakeError::invalid_key("public key is not hex"))?; + PublicKey::from_slice(&bytes) + .map_err(|_| WakeError::invalid_key("public key is not on the curve")) +} + +/// The scheme (`"ln"` or `"pk"`) of a canonical identity: +/// - `ln:` + 66 lowercase hex, a compressed secp256k1 key on the curve; +/// - `pk:` + 52 canonical z-base-32 characters, a canonical, non-weak +/// Ed25519 point. +fn identity_scheme(identity: &str) -> Result<&'static str, WakeError> { + let invalid = || WakeError::invalid_input("identities must be canonical ln: or pk: identities"); + if let Some(key) = identity.strip_prefix("ln:") { + parse_public_key_hex(key).map_err(|_| invalid())?; + return Ok("ln"); + } + let z32 = identity.strip_prefix("pk:").ok_or_else(invalid)?; + if z32.len() != 52 { + return Err(invalid()); + } + let key = pubky::PublicKey::try_from_z32(z32).map_err(|_| invalid())?; + let point = key.verifying_key(); + let canonical = key.z32() == z32 + && point.to_edwards().compress().to_bytes() == point.to_bytes() + && !point.is_weak(); + if !canonical { + return Err(invalid()); + } + Ok("pk") +} + +/// `^[a-z][a-z0-9_-]*(\.[A-Za-z][A-Za-z0-9_-]*)+$` names and `.*` +/// patterns, at most 64 characters. +fn is_valid_topic_filter(s: &str) -> bool { + if s.len() > MAX_TOPIC_LEN { + return false; + } + match s.strip_suffix(".*") { + Some(namespace) => is_namespace(namespace), + None => is_namespace(s) && s.contains('.'), + } +} + +/// `^[a-z][a-z0-9_-]*(\.[A-Za-z][A-Za-z0-9_-]*)*$` +fn is_namespace(s: &str) -> bool { + let mut segments = s.split('.'); + let first_ok = segments.next().is_some_and(|first| { + let mut bytes = first.bytes(); + bytes.next().is_some_and(|b| b.is_ascii_lowercase()) + && bytes.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_' || b == b'-') + }); + first_ok + && segments.all(|segment| { + let mut bytes = segment.bytes(); + bytes.next().is_some_and(|b| b.is_ascii_alphabetic()) + && bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-') + }) +} + +/// Encodes 32 bytes as a device secret: `wkd_` + 43 base64url characters. +pub(crate) fn device_secret_from_bytes(bytes: &[u8; 32]) -> WakeDeviceSecret { + let token = format!("{DEVICE_SECRET_PREFIX}{}", URL_SAFE_NO_PAD.encode(bytes)); + let sha256_hex = hex::encode(sha256::Hash::hash(token.as_bytes()).to_byte_array()); + WakeDeviceSecret { token, sha256_hex } +} + +/// Checks `wkd_` + 43 canonical base64url characters (32 bytes). +pub(crate) fn is_valid_device_secret(token: &str) -> bool { + token + .strip_prefix(DEVICE_SECRET_PREFIX) + .filter(|body| body.len() == 43) + .and_then(|body| URL_SAFE_NO_PAD.decode(body).ok()) + .is_some_and(|bytes| bytes.len() == 32) +} + +/// A fresh install id: 16 random bytes as 22 base64url characters. +pub(crate) fn install_id_from_bytes(bytes: &[u8; 16]) -> String { + URL_SAFE_NO_PAD.encode(bytes) +} + +/// Generates a device secret. Keep the token on the device; register its hash. +pub fn wake_generate_device_secret() -> Result { + let mut bytes = [0u8; 32]; + os_random(&mut bytes)?; + Ok(device_secret_from_bytes(&bytes)) +} + +/// Generates an install id, stable for the life of the install. +pub fn wake_generate_install_id() -> String { + let mut bytes = [0u8; 16]; + OsRng.fill_bytes(&mut bytes); + install_id_from_bytes(&bytes) +} + +/// Validates a registration and builds the message every identity signs. +/// `timestamp` defaults to now and must be within the gateway's skew window +/// (600 s) when the request is sent. +#[allow(clippy::too_many_arguments)] +pub fn wake_prepare_registration( + audience: String, + app: String, + install_id: String, + platform: WakePlatform, + environment: WakeEnvironment, + push_token: String, + encryption_public_key: String, + secret_sha256: String, + identities: Vec, + topics: Vec, + timestamp: Option, +) -> Result { + let timestamp = match timestamp { + Some(ts) => ts, + None => SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_| WakeError::invalid_input("the system clock is before 1970"))? + .as_secs(), + }; + let fields = RegistrationFields { + audience: &audience, + app: &app, + install_id: &install_id, + platform, + environment, + push_token: &push_token, + encryption_key: &encryption_public_key, + secret_sha256: &secret_sha256, + identities: &identities, + topics: &topics, + ts: timestamp, + }; + let message = fields.preimage()?; + validate(&fields)?; + Ok(WakeRegistrationRequest { + audience, + app, + install_id, + platform, + environment, + push_token, + encryption_public_key, + secret_sha256, + identities, + topics, + timestamp, + message, + }) +} + +/// Signs a registration message with a pubky Ed25519 secret key and returns +/// the 128-character lowercase hex signature. Only registration messages +/// are signed, so the key cannot be used here to sign anything else. +pub fn wake_sign_pubky_proof(secret_key_hex: String, message: String) -> Result { + if !message + .strip_prefix(PREIMAGE_TAG) + .is_some_and(|rest| rest.starts_with('\n')) + { + return Err(WakeError::invalid_input( + "only wake registration messages can be signed", + )); + } + let seed = parse_hex_32(&secret_key_hex) + .ok_or_else(|| WakeError::invalid_key("secret key must be 32 hex bytes"))?; + let keypair = pubky::Keypair::from_secret(&seed); + Ok(hex::encode(keypair.sign(message.as_bytes()).to_bytes())) +} diff --git a/src/modules/wake/test_vectors/legacy_registration.json b/src/modules/wake/test_vectors/legacy_registration.json new file mode 100644 index 0000000..e0e8325 --- /dev/null +++ b/src/modules/wake/test_vectors/legacy_registration.json @@ -0,0 +1,14 @@ +{ + "description": "Legacy POST /api/device signature: the node signs \"bitkit-notifications\" + deviceToken + isoTimestamp as a Lightning signed message (RFC 6979). Both encodings verify on the legacy route.", + "node_secret_key": "3333333333333333333333333333333333333333333333333333333333333333", + "node_id": "023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "device_token": "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0", + "iso_timestamp": "2024-01-01T00:00:00.000Z", + "message": "bitkit-notifications00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f02024-01-01T00:00:00.000Z", + "message_hash": "d71b311e68f324dfd117c3f3f868c4ba43b288351123765e58f8254b40fc7d2d", + "recovery_header": 31, + "signature_zbase32": "d7pcmmefqgy3qqi69fdsi3f6z7uot9m3bbe8baeciprhckdrscizyr7ks5yat84yn83zayon894qyijsxc6s5ufshs68ppa8sh1j5nwo", + "signature_hex": "1f5ac5ad057181973abef9476ae4bebf6708fd79085070e10cab49c62864b32b7013aab6c1889f4011f37c02023ff4e055367b3d6dccb6e5bc76b707b7249d8a90", + "device_secret_key": "2222222222222222222222222222222222222222222222222222222222222222", + "request_body": "{\"deviceToken\":\"00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0\",\"features\":[\"blocktank.incomingHtlc\",\"blocktank.mutualClose\",\"blocktank.orderPaymentConfirmed\",\"blocktank.cjitPaymentArrived\",\"blocktank.wakeToTimeout\"],\"isProduction\":true,\"isoTimestamp\":\"2024-01-01T00:00:00.000Z\",\"nodeId\":\"023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1\",\"publicKey\":\"02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27\",\"signature\":\"d7pcmmefqgy3qqi69fdsi3f6z7uot9m3bbe8baeciprhckdrscizyr7ks5yat84yn83zayon894qyijsxc6s5ufshs68ppa8sh1j5nwo\"}" +} diff --git a/src/modules/wake/test_vectors/pushes.json b/src/modules/wake/test_vectors/pushes.json new file mode 100644 index 0000000..3db3744 --- /dev/null +++ b/src/modules/wake/test_vectors/pushes.json @@ -0,0 +1,333 @@ +{ + "description": "Every provider request shape, byte-exact. APNs headers are listed in order; FCM bodies are POSTed to {fcm.base_url}/v1/projects/{project}/messages:send. Cases with device_secret_key decrypt to plaintext. inputs.collapse_key is the producer's collapse_key, which the gateway keeps to itself: pushes carry inputs.collapse_id, derived from it with collapse_secret. Topics with collapse = \"topic\" (every peer topic) pass none, so their background and FCM v1 pushes carry no collapse id.", + "collapse_rule": "collapse_id = the 64 lowercase hex characters of HMAC-SHA256(collapse_secret, identity + \"|\" + topic + \"|\" + collapse_key); the apns-collapse-id, FCM collapse_key and fallback tag are collapse(collapse_id), or collapse(wake id) where one is always sent; collapse(x) = x when it is 1 to 64 visible ASCII characters (0x21 to 0x7e), else the 64 hex characters of sha256(x)", + "collapse_secret": "7777777777777777777777777777777777777777777777777777777777777777", + "cases": [ + { + "name": "apns_v0", + "provider": "apns", + "inputs": { + "apns_topic": "to.bitkit", + "apns_id": "01a0c450-6c00-7000-8000-0000000000d1", + "expiration": 1790000060, + "container": "{\"cipher\":\"mvdDR3nQxKFTs/N1fQ5z38Zw079ynGfhKTULDq9Mzm96yPl4T4HRyvGLxpqEz9Kq/zeJnUbNzNNPFA2Kh5bPISCuuna+L375NLJgKEN65Aqu6q8KLXECOLhlgdXTEt38xl946MINUfY45uWdtO9h\",\"iv\":\"000102030405060708090a0b0c0d0e0f\",\"tag\":\"2bacdc4bd3ba62cdc3376e302dd70a5f\",\"publicKey\":\"034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa\"}" + }, + "headers": [ + [ + "apns-topic", + "to.bitkit" + ], + [ + "apns-id", + "01a0c450-6c00-7000-8000-0000000000d1" + ], + [ + "apns-expiration", + "1790000060" + ], + [ + "apns-push-type", + "alert" + ], + [ + "apns-priority", + "10" + ] + ], + "body": "{\"aps\":{\"badge\":0,\"sound\":\"ping.aiff\",\"alert\":{\"title\":\"Background Operation Request\",\"body\":\"Please open your wallet.\",\"payload\":{\"cipher\":\"mvdDR3nQxKFTs/N1fQ5z38Zw079ynGfhKTULDq9Mzm96yPl4T4HRyvGLxpqEz9Kq/zeJnUbNzNNPFA2Kh5bPISCuuna+L375NLJgKEN65Aqu6q8KLXECOLhlgdXTEt38xl946MINUfY45uWdtO9h\",\"iv\":\"000102030405060708090a0b0c0d0e0f\",\"tag\":\"2bacdc4bd3ba62cdc3376e302dd70a5f\",\"publicKey\":\"034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa\"}},\"content-available\":1,\"mutable-content\":1}}", + "body_len": 498, + "device_secret_key": "2222222222222222222222222222222222222222222222222222222222222222", + "plaintext": "{\"source\":\"blocktank\",\"type\":\"incomingHtlc\",\"payload\":{\"orderId\":\"abc\"},\"createdAt\":\"2024-01-01T00:00:00.000Z\"}" + }, + { + "name": "apns_v1_background", + "provider": "apns", + "inputs": { + "apns_topic": "to.bitkit", + "apns_id": "01a0c450-6c00-7000-8000-0000000000d2", + "expiration": 1790000060, + "wake_id": "01a0c450-6c00-7000-8000-000000000001", + "topic": "blocktank.incomingHtlc", + "identity": "ln:023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "collapse_key": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "collapse_id": "01708c8803f4e752df60858c2b4fae1c7a5a77df1be560dd5658e10c0b194085", + "container": "{\"v\":1,\"cipher\":\"bXIadw8XhVApCI59oieKY3P/h33PZz6NSdVFABqcg9CSgNhH5L+odPp6fRlxsbx/A37B0ZTxkuZgONflwXB4AyprdoQGEuDEbM8NSRT1j9uAzrfBR098QxyA5eA4nsKbginEF+oPHY97X0yzHZwxV7qd69X4SKve9PaXn4li383A1ZTqlcXJhtAWA7U5YHiFFrbiHAoQgUr7D01/z0vPKtHvryG+GD0rtFnrIzNwrwt6MH9hGO8gYHp1xPRO0bs432EnkOUO7rRv0JwD6q1VQ+GJrtEp/n8p5tW3QzOd8cexBUGoQM+Q6HlNHFRROjBM1fTIsaZj\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"4521e07642272d9b8aed5157c331ad7a\",\"publicKey\":\"035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c30\"}" + }, + "headers": [ + [ + "apns-topic", + "to.bitkit" + ], + [ + "apns-id", + "01a0c450-6c00-7000-8000-0000000000d2" + ], + [ + "apns-expiration", + "1790000060" + ], + [ + "apns-push-type", + "background" + ], + [ + "apns-priority", + "5" + ], + [ + "apns-collapse-id", + "01708c8803f4e752df60858c2b4fae1c7a5a77df1be560dd5658e10c0b194085" + ] + ], + "body": "{\"aps\":{\"content-available\":1},\"wake\":{\"v\":1,\"cipher\":\"bXIadw8XhVApCI59oieKY3P/h33PZz6NSdVFABqcg9CSgNhH5L+odPp6fRlxsbx/A37B0ZTxkuZgONflwXB4AyprdoQGEuDEbM8NSRT1j9uAzrfBR098QxyA5eA4nsKbginEF+oPHY97X0yzHZwxV7qd69X4SKve9PaXn4li383A1ZTqlcXJhtAWA7U5YHiFFrbiHAoQgUr7D01/z0vPKtHvryG+GD0rtFnrIzNwrwt6MH9hGO8gYHp1xPRO0bs432EnkOUO7rRv0JwD6q1VQ+GJrtEp/n8p5tW3QzOd8cexBUGoQM+Q6HlNHFRROjBM1fTIsaZj\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"4521e07642272d9b8aed5157c331ad7a\",\"publicKey\":\"035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c30\"}}", + "body_len": 540, + "device_secret_key": "5555555555555555555555555555555555555555555555555555555555555555", + "plaintext": "{\"v\":1,\"id\":\"01a0c450-6c00-7000-8000-000000000001\",\"topic\":\"blocktank.incomingHtlc\",\"urgency\":\"background\",\"deadline\":1790000060,\"payload\":{\"lspId\":\"blocktank-lsp\",\"paymentHash\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"}}" + }, + { + "name": "apns_v1_background_topic_collapse", + "provider": "apns", + "inputs": { + "apns_topic": "to.bitkit", + "apns_id": "01a0c450-6c00-7000-8000-0000000000d6", + "expiration": 1790000600, + "wake_id": "01a0c450-6c00-7000-8000-000000000004", + "topic": "paykit.sync", + "collapse_key": null, + "container": "{\"v\":1,\"cipher\":\"nMvhVZYC5nYbEsJ9PMETt49AfmbSzDKo5KDW7OCsyeEZmRP3PZHvTkQcQ3UGYV8d4hsdMaffBIiXd9mrYmxKmr5YeB/NwOuYYUHOT2CdRBUzDwXzwlCBvHQyjBwhSyhxSlwGEWXy4bok+/AEdFLcYgGPfevEhMK+GmGc45s9PR7//n2T1A==\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"eccbf52e56ed92385a2b5ae895a352ea\",\"publicKey\":\"02666bdf2025e32f41088899f2bcb4bf6983187f380e72fc7dee115b1f9957cc72\"}" + }, + "headers": [ + [ + "apns-topic", + "to.bitkit" + ], + [ + "apns-id", + "01a0c450-6c00-7000-8000-0000000000d6" + ], + [ + "apns-expiration", + "1790000600" + ], + [ + "apns-push-type", + "background" + ], + [ + "apns-priority", + "5" + ] + ], + "body": "{\"aps\":{\"content-available\":1},\"wake\":{\"v\":1,\"cipher\":\"nMvhVZYC5nYbEsJ9PMETt49AfmbSzDKo5KDW7OCsyeEZmRP3PZHvTkQcQ3UGYV8d4hsdMaffBIiXd9mrYmxKmr5YeB/NwOuYYUHOT2CdRBUzDwXzwlCBvHQyjBwhSyhxSlwGEWXy4bok+/AEdFLcYgGPfevEhMK+GmGc45s9PR7//n2T1A==\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"eccbf52e56ed92385a2b5ae895a352ea\",\"publicKey\":\"02666bdf2025e32f41088899f2bcb4bf6983187f380e72fc7dee115b1f9957cc72\"}}", + "body_len": 392, + "device_secret_key": "5555555555555555555555555555555555555555555555555555555555555555", + "plaintext": "{\"v\":1,\"id\":\"01a0c450-6c00-7000-8000-000000000004\",\"topic\":\"paykit.sync\",\"urgency\":\"background\",\"deadline\":1790000600,\"payload\":null}" + }, + { + "name": "apns_v1_alert", + "provider": "apns", + "inputs": { + "apns_topic": "to.bitkit", + "apns_id": "01a0c450-6c00-7000-8000-0000000000d3", + "expiration": 1790086400, + "wake_id": "01a0c450-6c00-7000-8000-000000000002", + "topic": "blocktank.mutualClose", + "collapse_key": null, + "title": "Spending Balance Expired", + "body": "Open Bitkit to move funds from spending to savings", + "interruption_level": "active", + "container": "{\"v\":1,\"cipher\":\"/2jwGSI3YmZGs/MT/ImtkUPt18COPSJjr5+aEYM2cWSHXGAQRlkSnjMKtwn/cGcS5jBCgNizbCDdWOwKrjVM8aO8G1+0rjOq/c4APXHNHQfO7kyPF8E54uvX2BKg09vDks3mP9VyfEOB4DoCyXJbLBiWJOr3ue+zIJ9Le34Z2q5YMx5VM0cp4OFPB6s8vSo9wpz8hDZebvNPYECnfZ0Y\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"24acda5546c94f893d9691094c96fb31\",\"publicKey\":\"02a1474ca48f28ec81e0c4bade52a4ccdf9ace80b438382464f14f7ba15ea3dc58\"}" + }, + "headers": [ + [ + "apns-topic", + "to.bitkit" + ], + [ + "apns-id", + "01a0c450-6c00-7000-8000-0000000000d3" + ], + [ + "apns-expiration", + "1790086400" + ], + [ + "apns-push-type", + "alert" + ], + [ + "apns-priority", + "10" + ], + [ + "apns-collapse-id", + "01a0c450-6c00-7000-8000-000000000002" + ] + ], + "body": "{\"aps\":{\"alert\":{\"title\":\"Spending Balance Expired\",\"body\":\"Open Bitkit to move funds from spending to savings\"},\"sound\":\"default\",\"mutable-content\":1,\"interruption-level\":\"active\",\"thread-id\":\"blocktank.mutualClose\"},\"wake\":{\"v\":1,\"cipher\":\"/2jwGSI3YmZGs/MT/ImtkUPt18COPSJjr5+aEYM2cWSHXGAQRlkSnjMKtwn/cGcS5jBCgNizbCDdWOwKrjVM8aO8G1+0rjOq/c4APXHNHQfO7kyPF8E54uvX2BKg09vDks3mP9VyfEOB4DoCyXJbLBiWJOr3ue+zIJ9Le34Z2q5YMx5VM0cp4OFPB6s8vSo9wpz8hDZebvNPYECnfZ0Y\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"24acda5546c94f893d9691094c96fb31\",\"publicKey\":\"02a1474ca48f28ec81e0c4bade52a4ccdf9ace80b438382464f14f7ba15ea3dc58\"}}", + "body_len": 611, + "device_secret_key": "5555555555555555555555555555555555555555555555555555555555555555", + "plaintext": "{\"v\":1,\"id\":\"01a0c450-6c00-7000-8000-000000000002\",\"topic\":\"blocktank.mutualClose\",\"urgency\":\"alert\",\"deadline\":1790086400,\"payload\":{\"lspId\":\"blocktank-lsp\"}}" + }, + { + "name": "apns_v1_time_sensitive", + "provider": "apns", + "inputs": { + "apns_topic": "to.bitkit", + "apns_id": "01a0c450-6c00-7000-8000-0000000000d4", + "expiration": 1790000060, + "wake_id": "01a0c450-6c00-7000-8000-000000000003", + "topic": "blocktank.incomingHtlc", + "identity": "ln:023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "collapse_key": "incomingHtlc:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "collapse_id": "572ecbf3b26e4181d933ff3c1593690288b85dd0f872af6897d29742d3b20dec", + "title": "Incoming Payment", + "body": "Open Bitkit now to receive your payment", + "interruption_level": "time-sensitive", + "container": "{\"v\":1,\"cipher\":\"N8vLNLPyiSvIytusmyeLOes0HH5PiRqiWP1PaZMnU7tzjAXeWkjOfPTHmd81o1zXjtqfj08qV6+nhebRqoFKATvSzGoUeKnhp8AGmAgNBjNklfgJx8GkKjFyaj00975BlBI4s3kfElILeqjis+6KhBHx/gcn+YHIpwyuA91bHEY5C07Nl0hy6jTGesU3h5pxBrSq+JvWz8EBKsVCAWNFAxW/FfRZWUWyFgtjwdl6b1gTM2MLQZ4lqTbftYTJQ7R++g4d/J9NdoDXHvF9gf95nI73NYlEqMFKUUmvMVy8l6eHOP3WFx9e4z/MAlY9O6id2MZrXugm8HoqPQ==\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"08e2e6ca1df355ca8907d3ba21245464\",\"publicKey\":\"03652d1b5efa70cb3b83a53cdf536172d205001f0dd54ca766a6a4ef766a48e6fb\"}" + }, + "headers": [ + [ + "apns-topic", + "to.bitkit" + ], + [ + "apns-id", + "01a0c450-6c00-7000-8000-0000000000d4" + ], + [ + "apns-expiration", + "1790000060" + ], + [ + "apns-push-type", + "alert" + ], + [ + "apns-priority", + "10" + ], + [ + "apns-collapse-id", + "572ecbf3b26e4181d933ff3c1593690288b85dd0f872af6897d29742d3b20dec" + ] + ], + "body": "{\"aps\":{\"alert\":{\"title\":\"Incoming Payment\",\"body\":\"Open Bitkit now to receive your payment\"},\"sound\":\"default\",\"mutable-content\":1,\"interruption-level\":\"time-sensitive\",\"thread-id\":\"blocktank.incomingHtlc\"},\"wake\":{\"v\":1,\"cipher\":\"N8vLNLPyiSvIytusmyeLOes0HH5PiRqiWP1PaZMnU7tzjAXeWkjOfPTHmd81o1zXjtqfj08qV6+nhebRqoFKATvSzGoUeKnhp8AGmAgNBjNklfgJx8GkKjFyaj00975BlBI4s3kfElILeqjis+6KhBHx/gcn+YHIpwyuA91bHEY5C07Nl0hy6jTGesU3h5pxBrSq+JvWz8EBKsVCAWNFAxW/FfRZWUWyFgtjwdl6b1gTM2MLQZ4lqTbftYTJQ7R++g4d/J9NdoDXHvF9gf95nI73NYlEqMFKUUmvMVy8l6eHOP3WFx9e4z/MAlY9O6id2MZrXugm8HoqPQ==\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"08e2e6ca1df355ca8907d3ba21245464\",\"publicKey\":\"03652d1b5efa70cb3b83a53cdf536172d205001f0dd54ca766a6a4ef766a48e6fb\"}}", + "body_len": 725, + "device_secret_key": "5555555555555555555555555555555555555555555555555555555555555555", + "plaintext": "{\"v\":1,\"id\":\"01a0c450-6c00-7000-8000-000000000003\",\"topic\":\"blocktank.incomingHtlc\",\"urgency\":\"time_sensitive\",\"deadline\":1790000060,\"payload\":{\"lspId\":\"blocktank-lsp\",\"paymentHash\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"}}" + }, + { + "name": "apns_fallback", + "provider": "apns", + "inputs": { + "apns_topic": "to.bitkit", + "apns_id": "01a0c450-6c00-7000-8000-0000000000d5", + "expiration": 1790000060, + "wake_id": "01a0c450-6c00-7000-8000-000000000001", + "topic": "blocktank.incomingHtlc", + "identity": "ln:023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "collapse_key": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "collapse_id": "01708c8803f4e752df60858c2b4fae1c7a5a77df1be560dd5658e10c0b194085", + "title": "Incoming Payment", + "body": "Open Bitkit now to receive your payment", + "interruption_level": "time-sensitive" + }, + "headers": [ + [ + "apns-topic", + "to.bitkit" + ], + [ + "apns-id", + "01a0c450-6c00-7000-8000-0000000000d5" + ], + [ + "apns-expiration", + "1790000060" + ], + [ + "apns-push-type", + "alert" + ], + [ + "apns-priority", + "10" + ], + [ + "apns-collapse-id", + "01708c8803f4e752df60858c2b4fae1c7a5a77df1be560dd5658e10c0b194085" + ] + ], + "body": "{\"aps\":{\"alert\":{\"title\":\"Incoming Payment\",\"body\":\"Open Bitkit now to receive your payment\"},\"sound\":\"default\",\"interruption-level\":\"time-sensitive\",\"thread-id\":\"blocktank.incomingHtlc\"},\"wake_fallback\":{\"v\":1,\"id\":\"01a0c450-6c00-7000-8000-000000000001\",\"topic\":\"blocktank.incomingHtlc\"}}", + "body_len": 289 + }, + { + "name": "fcm_v0", + "provider": "fcm", + "inputs": { + "token": "e5Hj9fKqTmW-vector:APA91bG0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "ttl_secs": 60, + "container": "{\"cipher\":\"mvdDR3nQxKFTs/N1fQ5z38Zw079ynGfhKTULDq9Mzm96yPl4T4HRyvGLxpqEz9Kq/zeJnUbNzNNPFA2Kh5bPISCuuna+L375NLJgKEN65Aqu6q8KLXECOLhlgdXTEt38xl946MINUfY45uWdtO9h\",\"iv\":\"000102030405060708090a0b0c0d0e0f\",\"tag\":\"2bacdc4bd3ba62cdc3376e302dd70a5f\",\"publicKey\":\"034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa\"}" + }, + "body": "{\"message\":{\"token\":\"e5Hj9fKqTmW-vector:APA91bG0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"android\":{\"priority\":\"high\",\"ttl\":\"60s\",\"data\":{\"cipher\":\"mvdDR3nQxKFTs/N1fQ5z38Zw079ynGfhKTULDq9Mzm96yPl4T4HRyvGLxpqEz9Kq/zeJnUbNzNNPFA2Kh5bPISCuuna+L375NLJgKEN65Aqu6q8KLXECOLhlgdXTEt38xl946MINUfY45uWdtO9h\",\"iv\":\"000102030405060708090a0b0c0d0e0f\",\"tag\":\"2bacdc4bd3ba62cdc3376e302dd70a5f\",\"publicKey\":\"034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa\",\"title\":\"Background Operation Request\",\"message\":\"Please open your wallet.\",\"sound\":\"default\"}},\"data\":{\"cipher\":\"mvdDR3nQxKFTs/N1fQ5z38Zw079ynGfhKTULDq9Mzm96yPl4T4HRyvGLxpqEz9Kq/zeJnUbNzNNPFA2Kh5bPISCuuna+L375NLJgKEN65Aqu6q8KLXECOLhlgdXTEt38xl946MINUfY45uWdtO9h\",\"iv\":\"000102030405060708090a0b0c0d0e0f\",\"tag\":\"2bacdc4bd3ba62cdc3376e302dd70a5f\",\"publicKey\":\"034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa\"}}}", + "body_len": 944, + "device_secret_key": "2222222222222222222222222222222222222222222222222222222222222222", + "plaintext": "{\"source\":\"blocktank\",\"type\":\"incomingHtlc\",\"payload\":{\"orderId\":\"abc\"},\"createdAt\":\"2024-01-01T00:00:00.000Z\"}" + }, + { + "name": "fcm_v1", + "provider": "fcm", + "inputs": { + "token": "e5Hj9fKqTmW-vector:APA91bG0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "ttl_secs": 60, + "priority": "high", + "wake_id": "01a0c450-6c00-7000-8000-000000000001", + "topic": "blocktank.incomingHtlc", + "identity": "ln:023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "collapse_key": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "collapse_id": "01708c8803f4e752df60858c2b4fae1c7a5a77df1be560dd5658e10c0b194085", + "container": "{\"v\":1,\"cipher\":\"bXIadw8XhVApCI59oieKY3P/h33PZz6NSdVFABqcg9CSgNhH5L+odPp6fRlxsbx/A37B0ZTxkuZgONflwXB4AyprdoQGEuDEbM8NSRT1j9uAzrfBR098QxyA5eA4nsKbginEF+oPHY97X0yzHZwxV7qd69X4SKve9PaXn4li383A1ZTqlcXJhtAWA7U5YHiFFrbiHAoQgUr7D01/z0vPKtHvryG+GD0rtFnrIzNwrwt6MH9hGO8gYHp1xPRO0bs432EnkOUO7rRv0JwD6q1VQ+GJrtEp/n8p5tW3QzOd8cexBUGoQM+Q6HlNHFRROjBM1fTIsaZj\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"4521e07642272d9b8aed5157c331ad7a\",\"publicKey\":\"035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c30\"}" + }, + "body": "{\"message\":{\"token\":\"e5Hj9fKqTmW-vector:APA91bG0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"android\":{\"priority\":\"high\",\"ttl\":\"60s\",\"collapse_key\":\"01708c8803f4e752df60858c2b4fae1c7a5a77df1be560dd5658e10c0b194085\"},\"data\":{\"wake\":\"{\\\"v\\\":1,\\\"cipher\\\":\\\"bXIadw8XhVApCI59oieKY3P/h33PZz6NSdVFABqcg9CSgNhH5L+odPp6fRlxsbx/A37B0ZTxkuZgONflwXB4AyprdoQGEuDEbM8NSRT1j9uAzrfBR098QxyA5eA4nsKbginEF+oPHY97X0yzHZwxV7qd69X4SKve9PaXn4li383A1ZTqlcXJhtAWA7U5YHiFFrbiHAoQgUr7D01/z0vPKtHvryG+GD0rtFnrIzNwrwt6MH9hGO8gYHp1xPRO0bs432EnkOUO7rRv0JwD6q1VQ+GJrtEp/n8p5tW3QzOd8cexBUGoQM+Q6HlNHFRROjBM1fTIsaZj\\\",\\\"iv\\\":\\\"000102030405060708090a0b\\\",\\\"tag\\\":\\\"4521e07642272d9b8aed5157c331ad7a\\\",\\\"publicKey\\\":\\\"035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c30\\\"}\"}}}", + "body_len": 808, + "device_secret_key": "5555555555555555555555555555555555555555555555555555555555555555", + "plaintext": "{\"v\":1,\"id\":\"01a0c450-6c00-7000-8000-000000000001\",\"topic\":\"blocktank.incomingHtlc\",\"urgency\":\"background\",\"deadline\":1790000060,\"payload\":{\"lspId\":\"blocktank-lsp\",\"paymentHash\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"}}" + }, + { + "name": "fcm_v1_topic_collapse", + "provider": "fcm", + "inputs": { + "token": "e5Hj9fKqTmW-vector:APA91bG0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "ttl_secs": 600, + "priority": "normal", + "wake_id": "01a0c450-6c00-7000-8000-000000000004", + "topic": "paykit.sync", + "collapse_key": null, + "container": "{\"v\":1,\"cipher\":\"nMvhVZYC5nYbEsJ9PMETt49AfmbSzDKo5KDW7OCsyeEZmRP3PZHvTkQcQ3UGYV8d4hsdMaffBIiXd9mrYmxKmr5YeB/NwOuYYUHOT2CdRBUzDwXzwlCBvHQyjBwhSyhxSlwGEWXy4bok+/AEdFLcYgGPfevEhMK+GmGc45s9PR7//n2T1A==\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"eccbf52e56ed92385a2b5ae895a352ea\",\"publicKey\":\"02666bdf2025e32f41088899f2bcb4bf6983187f380e72fc7dee115b1f9957cc72\"}" + }, + "body": "{\"message\":{\"token\":\"e5Hj9fKqTmW-vector:APA91bG0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"android\":{\"priority\":\"normal\",\"ttl\":\"600s\"},\"data\":{\"wake\":\"{\\\"v\\\":1,\\\"cipher\\\":\\\"nMvhVZYC5nYbEsJ9PMETt49AfmbSzDKo5KDW7OCsyeEZmRP3PZHvTkQcQ3UGYV8d4hsdMaffBIiXd9mrYmxKmr5YeB/NwOuYYUHOT2CdRBUzDwXzwlCBvHQyjBwhSyhxSlwGEWXy4bok+/AEdFLcYgGPfevEhMK+GmGc45s9PR7//n2T1A==\\\",\\\"iv\\\":\\\"000102030405060708090a0b\\\",\\\"tag\\\":\\\"eccbf52e56ed92385a2b5ae895a352ea\\\",\\\"publicKey\\\":\\\"02666bdf2025e32f41088899f2bcb4bf6983187f380e72fc7dee115b1f9957cc72\\\"}\"}}}", + "body_len": 581, + "device_secret_key": "5555555555555555555555555555555555555555555555555555555555555555", + "plaintext": "{\"v\":1,\"id\":\"01a0c450-6c00-7000-8000-000000000004\",\"topic\":\"paykit.sync\",\"urgency\":\"background\",\"deadline\":1790000600,\"payload\":null}" + }, + { + "name": "fcm_fallback", + "provider": "fcm", + "inputs": { + "token": "e5Hj9fKqTmW-vector:APA91bG0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "ttl_secs": 45, + "wake_id": "01a0c450-6c00-7000-8000-000000000001", + "topic": "blocktank.incomingHtlc", + "identity": "ln:023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "collapse_key": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "collapse_id": "01708c8803f4e752df60858c2b4fae1c7a5a77df1be560dd5658e10c0b194085", + "title": "Incoming Payment", + "body": "Open Bitkit now to receive your payment", + "channel_id": "wake" + }, + "body": "{\"message\":{\"token\":\"e5Hj9fKqTmW-vector:APA91bG0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"android\":{\"priority\":\"high\",\"ttl\":\"45s\",\"notification\":{\"title\":\"Incoming Payment\",\"body\":\"Open Bitkit now to receive your payment\",\"channel_id\":\"wake\",\"tag\":\"01708c8803f4e752df60858c2b4fae1c7a5a77df1be560dd5658e10c0b194085\"}},\"data\":{\"wake_fallback\":\"{\\\"v\\\":1,\\\"id\\\":\\\"01a0c450-6c00-7000-8000-000000000001\\\",\\\"topic\\\":\\\"blocktank.incomingHtlc\\\"}\"}}}", + "body_len": 496 + } + ] +} diff --git a/src/modules/wake/test_vectors/registration_v1.json b/src/modules/wake/test_vectors/registration_v1.json new file mode 100644 index 0000000..b8ef817 --- /dev/null +++ b/src/modules/wake/test_vectors/registration_v1.json @@ -0,0 +1,45 @@ +{ + "description": "POST /v1/devices: the 12-line preimage every identity signs, an ln: proof (Lightning signed message, zbase32) and a pk: proof (Ed25519, hex) over it, and the full request body.", + "audience": "wake.localhost", + "encryption_secret_key": "5555555555555555555555555555555555555555555555555555555555555555", + "device_secret_bytes": "7777777777777777777777777777777777777777777777777777777777777777", + "device_secret": "wkd_d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3c", + "ln_secret_key": "3333333333333333333333333333333333333333333333333333333333333333", + "ln_identity": "ln:023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "pk_seed": "4444444444444444444444444444444444444444444444444444444444444444", + "pk_public_key": "d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48", + "pk_identity": "pk:47cz1q7hnqtedgwnxt5k5pz5in1ji5oyx7r9fwr3fsc5ojppftry", + "preimage": "wake-register-v1\naud:wake.localhost\napp:bitkit\ninstall:AAAAAAAAAAAAAAAAAAAAAA\nplatform:apns\nenvironment:sandbox\npush_token:00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0\nencryption_key:029ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b\nsecret_sha256:a4908532a2f65068ce0b3498af2b68e43949be33aa85840288e653295bf52080\nidentities:ln:023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1,pk:47cz1q7hnqtedgwnxt5k5pz5in1ji5oyx7r9fwr3fsc5ojppftry\ntopics:blocktank.*,paykit.sync\nts:1790000000", + "preimage_sha256": "24a1e86c7c81b6b03c755fa7e18cba5147687fede582e9d0f25a44abd8d00a61", + "ln_message_hash": "d8af6af5731fb4e049049a68dcf4ab7161c1d817499c58d08a467c36301f4b83", + "ln_proof": "rd8ooicd9c6ykxwred545hmdrnnu7nwy6ifj151f9f4cycw5znifgcexjt6bqjwf8c6xfdgzwrs7tfn5o5jcwhdpepif68fwjwu9z4yb", + "pk_proof": "1412afa4780e7db59294f0dca42dda93248b54a4c0d4b29c4d6b35bd9d270826faf66d7b9b3ccf6b660ac32313902c8934731ff2fd288cca6ebf96689abe630d", + "request": { + "app": "bitkit", + "install_id": "AAAAAAAAAAAAAAAAAAAAAA", + "platform": "apns", + "environment": "sandbox", + "push_token": "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0", + "encryption_key": "029ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b", + "secret_sha256": "a4908532a2f65068ce0b3498af2b68e43949be33aa85840288e653295bf52080", + "identities": [ + "pk:47cz1q7hnqtedgwnxt5k5pz5in1ji5oyx7r9fwr3fsc5ojppftry", + "ln:023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1" + ], + "topics": [ + "paykit.sync", + "blocktank.*" + ], + "ts": 1790000000, + "proofs": [ + { + "identity": "ln:023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "sig": "rd8ooicd9c6ykxwred545hmdrnnu7nwy6ifj151f9f4cycw5znifgcexjt6bqjwf8c6xfdgzwrs7tfn5o5jcwhdpepif68fwjwu9z4yb" + }, + { + "identity": "pk:47cz1q7hnqtedgwnxt5k5pz5in1ji5oyx7r9fwr3fsc5ojppftry", + "sig": "1412afa4780e7db59294f0dca42dda93248b54a4c0d4b29c4d6b35bd9d270826faf66d7b9b3ccf6b660ac32313902c8934731ff2fd288cca6ebf96689abe630d" + } + ] + } +} diff --git a/src/modules/wake/test_vectors/v0_client.json b/src/modules/wake/test_vectors/v0_client.json new file mode 100644 index 0000000..3967c29 --- /dev/null +++ b/src/modules/wake/test_vectors/v0_client.json @@ -0,0 +1,15 @@ +{ + "description": "Client-side v0 decryption vector from bitkit-ios BitkitTests/CryptoTests.swift and bitkit-android CryptoTest.kt. The container is assembled from the vector's fields in v0 key order.", + "scheme": "v0", + "label": "bitkit-notifications", + "device_secret_key": "cc74b1a4fdcd35916c766d3318c5a93b7e33a36ebeff0463128bf284975c2680", + "device_public_key": "02f7c231fe139475b8193194b42043ae546768167e5cf836f5a4063f5d448a127f", + "ephemeral_public_key": "031e9923e689a181a803486b7d8c0d4a5aad360edb70c8bb413a98458d91652213", + "shared_point": "028ce542975d6d7b2307c92e527d507b03ffb3d897eb2e0830d29f40d5efd80ee3", + "key": "3a9d552cb16dfae40feae644254c4ca46cab82e570de5662aacc4018e33b609b", + "iv": "2b8ed77fd2198e3ed88cfaa794a246e8", + "tag": "caddd13746d6a6aed16176734964d3a3", + "cipher": "l2fInfyw64gO12odo8iipISloQJ45Rc4WjFmpe95brdaAMDq+T/L9ZChcmMCXnR0J6BXd8sSIJe/0bmby8uSZZJuVCzwF76XHfY5oq0Y1/hKzyZTn8nG3dqfiLHnAPy1tZFQfm5ALgjwWnViYJLXoGFpXs7kLMA=", + "container": "{\"cipher\":\"l2fInfyw64gO12odo8iipISloQJ45Rc4WjFmpe95brdaAMDq+T/L9ZChcmMCXnR0J6BXd8sSIJe/0bmby8uSZZJuVCzwF76XHfY5oq0Y1/hKzyZTn8nG3dqfiLHnAPy1tZFQfm5ALgjwWnViYJLXoGFpXs7kLMA=\",\"iv\":\"2b8ed77fd2198e3ed88cfaa794a246e8\",\"tag\":\"caddd13746d6a6aed16176734964d3a3\",\"publicKey\":\"031e9923e689a181a803486b7d8c0d4a5aad360edb70c8bb413a98458d91652213\"}", + "plaintext": "{\"source\":\"blocktank\",\"type\":\"incomingHtlc\",\"payload\":{\"secretMessage\":\"hello\"},\"createdAt\":\"2024-09-18T13:33:52.555Z\"}" +} diff --git a/src/modules/wake/test_vectors/v0_server.json b/src/modules/wake/test_vectors/v0_server.json new file mode 100644 index 0000000..b66b287 --- /dev/null +++ b/src/modules/wake/test_vectors/v0_server.json @@ -0,0 +1,21 @@ +{ + "description": "Legacy (v0) envelope as the gateway builds it for deployed Bitkit apps; byte-identical to bitkit-notification-server. The ephemeral key is the per-notification server key.", + "scheme": "v0", + "label": "bitkit-notifications", + "ephemeral_secret_key": "1111111111111111111111111111111111111111111111111111111111111111", + "ephemeral_public_key": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", + "device_secret_key": "2222222222222222222222222222222222222222222222222222222222222222", + "device_public_key": "02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27", + "shared_point": "0277e0510d5042e2f5e9e59c977b81eeed590cf7d20c1c51da451a8eaa9fdc45ff", + "key": "954b48c50f1c6bf3a6cd20a6d7354775cb0d712a3e332333c87dcd52b83f42f9", + "iv": "000102030405060708090a0b0c0d0e0f", + "topic": "blocktank.incomingHtlc", + "payload": "{\"orderId\":\"abc\"}", + "created_at": "2024-01-01T00:00:00.000Z", + "plaintext": "{\"source\":\"blocktank\",\"type\":\"incomingHtlc\",\"payload\":{\"orderId\":\"abc\"},\"createdAt\":\"2024-01-01T00:00:00.000Z\"}", + "plaintext_len": 111, + "cipher": "mvdDR3nQxKFTs/N1fQ5z38Zw079ynGfhKTULDq9Mzm96yPl4T4HRyvGLxpqEz9Kq/zeJnUbNzNNPFA2Kh5bPISCuuna+L375NLJgKEN65Aqu6q8KLXECOLhlgdXTEt38xl946MINUfY45uWdtO9h", + "tag": "2bacdc4bd3ba62cdc3376e302dd70a5f", + "container": "{\"cipher\":\"mvdDR3nQxKFTs/N1fQ5z38Zw079ynGfhKTULDq9Mzm96yPl4T4HRyvGLxpqEz9Kq/zeJnUbNzNNPFA2Kh5bPISCuuna+L375NLJgKEN65Aqu6q8KLXECOLhlgdXTEt38xl946MINUfY45uWdtO9h\",\"iv\":\"000102030405060708090a0b0c0d0e0f\",\"tag\":\"2bacdc4bd3ba62cdc3376e302dd70a5f\",\"publicKey\":\"034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa\"}", + "container_len": 323 +} diff --git a/src/modules/wake/test_vectors/v1_envelope.json b/src/modules/wake/test_vectors/v1_envelope.json new file mode 100644 index 0000000..d064e7b --- /dev/null +++ b/src/modules/wake/test_vectors/v1_envelope.json @@ -0,0 +1,18 @@ +{ + "description": "wake v1 envelope: S33 = compressed(ephemeral * device), key = SHA256(SHA256(S33 || \"wake-v1\")), AES-256-GCM with a 12-byte IV and no AAD.", + "scheme": "v1", + "label": "wake-v1", + "ephemeral_secret_key": "6666666666666666666666666666666666666666666666666666666666666666", + "ephemeral_public_key": "035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c30", + "device_secret_key": "5555555555555555555555555555555555555555555555555555555555555555", + "device_public_key": "029ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b", + "shared_point": "02c88d18b3ab2342f968f14ba920a9f47aeb8437a3ac494f4d263a79dd29b75353", + "key": "71ab2d520b559d67a72bbab49dd521fa10a9a6416a55d9dca42b007eca7c5e2e", + "iv": "000102030405060708090a0b", + "plaintext": "{\"v\":1,\"id\":\"01a0c450-6c00-7000-8000-000000000001\",\"topic\":\"blocktank.incomingHtlc\",\"urgency\":\"background\",\"deadline\":1790000060,\"payload\":{\"lspId\":\"blocktank-lsp\",\"paymentHash\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"}}", + "plaintext_len": 246, + "cipher": "bXIadw8XhVApCI59oieKY3P/h33PZz6NSdVFABqcg9CSgNhH5L+odPp6fRlxsbx/A37B0ZTxkuZgONflwXB4AyprdoQGEuDEbM8NSRT1j9uAzrfBR098QxyA5eA4nsKbginEF+oPHY97X0yzHZwxV7qd69X4SKve9PaXn4li383A1ZTqlcXJhtAWA7U5YHiFFrbiHAoQgUr7D01/z0vPKtHvryG+GD0rtFnrIzNwrwt6MH9hGO8gYHp1xPRO0bs432EnkOUO7rRv0JwD6q1VQ+GJrtEp/n8p5tW3QzOd8cexBUGoQM+Q6HlNHFRROjBM1fTIsaZj", + "tag": "4521e07642272d9b8aed5157c331ad7a", + "container": "{\"v\":1,\"cipher\":\"bXIadw8XhVApCI59oieKY3P/h33PZz6NSdVFABqcg9CSgNhH5L+odPp6fRlxsbx/A37B0ZTxkuZgONflwXB4AyprdoQGEuDEbM8NSRT1j9uAzrfBR098QxyA5eA4nsKbginEF+oPHY97X0yzHZwxV7qd69X4SKve9PaXn4li383A1ZTqlcXJhtAWA7U5YHiFFrbiHAoQgUr7D01/z0vPKtHvryG+GD0rtFnrIzNwrwt6MH9hGO8gYHp1xPRO0bs432EnkOUO7rRv0JwD6q1VQ+GJrtEp/n8p5tW3QzOd8cexBUGoQM+Q6HlNHFRROjBM1fTIsaZj\",\"iv\":\"000102030405060708090a0b\",\"tag\":\"4521e07642272d9b8aed5157c331ad7a\",\"publicKey\":\"035ab4689e400a4a160cf01cd44730845a54768df8547dcdf073d964f109f18c30\"}", + "container_len": 501 +} diff --git a/src/modules/wake/tests.rs b/src/modules/wake/tests.rs new file mode 100644 index 0000000..1fd9827 --- /dev/null +++ b/src/modules/wake/tests.rs @@ -0,0 +1,1779 @@ +use bitcoin::hashes::{sha256, Hash}; +use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, SecretKey}; +use reqwest::Method; +use serde_json::{json, Value}; + +use super::client::*; +use super::crypto::*; +use super::push::*; +use super::registration::*; +use super::*; + +// The golden vectors of wake-proto, copied byte for byte (see README.md). +const V0_SERVER: &str = include_str!("test_vectors/v0_server.json"); +const V0_CLIENT: &str = include_str!("test_vectors/v0_client.json"); +const V1_ENVELOPE: &str = include_str!("test_vectors/v1_envelope.json"); +const REGISTRATION_V1: &str = include_str!("test_vectors/registration_v1.json"); +const LEGACY_REGISTRATION: &str = include_str!("test_vectors/legacy_registration.json"); +const PUSHES: &str = include_str!("test_vectors/pushes.json"); + +const GATEWAY: &str = "https://wake.example"; +const DEVICE_SECRET: &str = "wkd_d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3c"; +const WAKE_ID: &str = "01a0c450-6c00-7000-8000-000000000001"; + +fn load(text: &str) -> Value { + serde_json::from_str(text).unwrap() +} + +fn s<'a>(v: &'a Value, key: &str) -> &'a str { + v[key] + .as_str() + .unwrap_or_else(|| panic!("missing string {key}")) +} + +fn secret(v: &Value, key: &str) -> SecretKey { + parse_secret_key(s(v, key)).unwrap() +} + +fn public_hex(secret: &SecretKey) -> String { + hex::encode(PublicKey::from_secret_key(&Secp256k1::signing_only(), secret).serialize()) +} + +fn bytes33(hex_str: &str) -> [u8; 33] { + hex::decode(hex_str).unwrap().try_into().unwrap() +} + +fn invalid_input(reason: &str) -> WakeError { + WakeError::InvalidInput { + reason: reason.to_string(), + } +} + +fn unsupported(reason: &str) -> WakeError { + WakeError::UnsupportedEnvelope { + reason: reason.to_string(), + } +} + +fn decryption_failed() -> WakeError { + WakeError::DecryptionFailed { + reason: "authentication failed".to_string(), + } +} + +/// `SHA256(SHA256("Lightning Signed Message:" || message))`. +fn lightning_message_hash(message: &[u8]) -> [u8; 32] { + let mut input = b"Lightning Signed Message:".to_vec(); + input.extend_from_slice(message); + sha256::Hash::hash(sha256::Hash::hash(&input).as_byte_array()).to_byte_array() +} + +/// A local stand-in for ldk-node `signMessage`: a recoverable RFC 6979 +/// signature, `[31 + recid] || r || s`, in zbase32. +fn lightning_sign(secret: &SecretKey, message: &[u8]) -> String { + let digest = Message::from_digest(lightning_message_hash(message)); + let (recovery_id, compact) = Secp256k1::signing_only() + .sign_ecdsa_recoverable(&digest, secret) + .serialize_compact(); + let mut bytes = vec![31 + recovery_id.to_i32() as u8]; + bytes.extend_from_slice(&compact); + zbase32(&bytes) +} + +fn zbase32(bytes: &[u8]) -> String { + const ALPHABET: &[u8; 32] = b"ybndrfg8ejkmcpqxot1uwisza345h769"; + let mut out = String::new(); + let (mut buffer, mut bits) = (0u32, 0u32); + for &byte in bytes { + buffer = (buffer << 8) | u32::from(byte); + bits += 8; + while bits >= 5 { + bits -= 5; + out.push(ALPHABET[((buffer >> bits) & 31) as usize] as char); + } + buffer &= (1 << bits) - 1; + } + if bits > 0 { + out.push(ALPHABET[((buffer << (5 - bits)) & 31) as usize] as char); + } + out +} + +/// Checks an envelope vector's intermediates from the file alone: the +/// device's view (S33 from its secret and the ephemeral key), the key, the +/// container round trip and the plaintext. +fn check_envelope_vector(v: &Value, scheme: Scheme) { + let device = secret(v, "device_secret_key"); + assert_eq!(public_hex(&device), s(v, "device_public_key")); + let shared = shared_point(&device, &bytes33(s(v, "ephemeral_public_key"))).unwrap(); + assert_eq!(hex::encode(shared), s(v, "shared_point")); + assert_eq!(s(v, "label"), scheme.label()); + assert_eq!( + hex::encode(derive_key(&shared, scheme.label())), + s(v, "key") + ); + if v.get("ephemeral_secret_key").is_some() { + let ephemeral = secret(v, "ephemeral_secret_key"); + assert_eq!(public_hex(&ephemeral), s(v, "ephemeral_public_key")); + let from_gateway = shared_point(&ephemeral, &bytes33(s(v, "device_public_key"))).unwrap(); + assert_eq!(from_gateway, shared); + } + let container = Container::from_json(s(v, "container")).unwrap(); + assert_eq!(container.scheme(), scheme); + assert_eq!(container.to_json(), s(v, "container")); + let plaintext = String::from_utf8(container.decrypt(&device).unwrap()).unwrap(); + assert_eq!(plaintext, s(v, "plaintext")); +} + +// ----- (a) v0 server envelope ----- + +#[test] +fn v0_server_vector() { + let v = load(V0_SERVER); + check_envelope_vector(&v, Scheme::V0); + assert_eq!( + s(&v, "shared_point"), + "0277e0510d5042e2f5e9e59c977b81eeed590cf7d20c1c51da451a8eaa9fdc45ff" + ); + assert_eq!( + s(&v, "key"), + "954b48c50f1c6bf3a6cd20a6d7354775cb0d712a3e332333c87dcd52b83f42f9" + ); + + // Encrypting the plaintext the way the gateway does gives the same bytes. + let rebuilt = seal_deterministic( + Scheme::V0, + &bytes33(s(&v, "device_public_key")), + s(&v, "plaintext").as_bytes(), + &secret(&v, "ephemeral_secret_key"), + &hex::decode(s(&v, "iv")).unwrap(), + ); + assert_eq!(rebuilt.to_json(), s(&v, "container")); + assert_eq!(rebuilt.to_json().len(), 323); + assert_eq!(v["container_len"], 323); + + let envelope = wake_decrypt_v0( + s(&v, "device_secret_key").to_string(), + s(&v, "cipher").to_string(), + s(&v, "iv").to_string(), + s(&v, "tag").to_string(), + s(&v, "ephemeral_public_key").to_string(), + ) + .unwrap(); + assert_eq!( + envelope, + WakeEnvelope { + version: 0, + id: None, + topic: s(&v, "topic").to_string(), + urgency: None, + deadline: None, + payload_json: Some(s(&v, "payload").to_string()), + created_at: Some(s(&v, "created_at").to_string()), + fallback: false, + } + ); +} + +// ----- (b) v0 client vector from the apps ----- + +#[test] +fn v0_client_vector() { + let v = load(V0_CLIENT); + check_envelope_vector(&v, Scheme::V0); + assert_eq!( + s(&v, "shared_point"), + "028ce542975d6d7b2307c92e527d507b03ffb3d897eb2e0830d29f40d5efd80ee3" + ); + assert_eq!( + s(&v, "key"), + "3a9d552cb16dfae40feae644254c4ca46cab82e570de5662aacc4018e33b609b" + ); + + let expected = WakeEnvelope { + version: 0, + id: None, + topic: "blocktank.incomingHtlc".to_string(), + urgency: None, + deadline: None, + payload_json: Some(r#"{"secretMessage":"hello"}"#.to_string()), + created_at: Some("2024-09-18T13:33:52.555Z".to_string()), + fallback: false, + }; + let envelope = wake_decrypt_v0( + s(&v, "device_secret_key").to_string(), + s(&v, "cipher").to_string(), + s(&v, "iv").to_string(), + s(&v, "tag").to_string(), + s(&v, "ephemeral_public_key").to_string(), + ) + .unwrap(); + assert_eq!(envelope, expected); + + // The same envelope as each platform delivers it. + let container: Value = serde_json::from_str(s(&v, "container")).unwrap(); + let apns = json!({"aps": {"alert": {"title": "t", "body": "b", "payload": container}}}); + let mut fcm = container.clone(); + fcm["title"] = json!("Background Operation Request"); + fcm["message"] = json!("Please open your wallet."); + fcm["sound"] = json!("default"); + for push in [apns, fcm] { + let envelope = + wake_decrypt_push(s(&v, "device_secret_key").to_string(), push.to_string()).unwrap(); + assert_eq!(envelope, expected, "{push}"); + } +} + +// ----- (d) v1 envelope ----- + +#[test] +fn v1_envelope_vector() { + let v = load(V1_ENVELOPE); + check_envelope_vector(&v, Scheme::V1); + + let rebuilt = seal_deterministic( + Scheme::V1, + &bytes33(s(&v, "device_public_key")), + s(&v, "plaintext").as_bytes(), + &secret(&v, "ephemeral_secret_key"), + &hex::decode(s(&v, "iv")).unwrap(), + ); + assert_eq!(rebuilt.to_json(), s(&v, "container")); + assert_eq!(rebuilt.to_json().len(), 501); + + let envelope = wake_decrypt_v1( + s(&v, "device_secret_key").to_string(), + s(&v, "container").to_string(), + ) + .unwrap(); + assert_eq!( + envelope, + WakeEnvelope { + version: 1, + id: Some(WAKE_ID.to_string()), + topic: "blocktank.incomingHtlc".to_string(), + urgency: Some("background".to_string()), + deadline: Some(1790000060), + payload_json: Some( + r#"{"lspId":"blocktank-lsp","paymentHash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}"# + .to_string() + ), + created_at: None, + fallback: false, + } + ); +} + +// ----- (e) registration preimage and proofs ----- + +fn vector_registration(v: &Value) -> WakeRegistrationRequest { + let request = &v["request"]; + let strings = |key: &str| -> Vec { + request[key] + .as_array() + .unwrap() + .iter() + .map(|i| i.as_str().unwrap().to_string()) + .collect() + }; + wake_prepare_registration( + s(v, "audience").to_string(), + s(request, "app").to_string(), + s(request, "install_id").to_string(), + WakePlatform::Apns, + WakeEnvironment::Sandbox, + s(request, "push_token").to_string(), + s(request, "encryption_key").to_string(), + s(request, "secret_sha256").to_string(), + strings("identities"), + strings("topics"), + request["ts"].as_u64(), + ) + .unwrap() +} + +#[test] +fn registration_v1_vector() { + let v = load(REGISTRATION_V1); + let registration = vector_registration(&v); + assert_eq!(registration.message, s(&v, "preimage")); + assert_eq!(registration.message.lines().count(), 12); + assert_eq!( + hex::encode(sha256::Hash::hash(registration.message.as_bytes()).to_byte_array()), + s(&v, "preimage_sha256") + ); + assert_eq!( + hex::encode(lightning_message_hash(registration.message.as_bytes())), + s(&v, "ln_message_hash") + ); + + // The credentials the request carries. + let device_secret = device_secret_from_bytes( + &hex::decode(s(&v, "device_secret_bytes")) + .unwrap() + .try_into() + .unwrap(), + ); + assert_eq!(device_secret.token, s(&v, "device_secret")); + assert_eq!(device_secret.sha256_hex, registration.secret_sha256); + assert_eq!( + public_hex(&secret(&v, "encryption_secret_key")), + registration.encryption_public_key + ); + let ln_secret = secret(&v, "ln_secret_key"); + assert_eq!( + format!("ln:{}", public_hex(&ln_secret)), + s(&v, "ln_identity") + ); + let pk_seed: [u8; 32] = hex::decode(s(&v, "pk_seed")).unwrap().try_into().unwrap(); + let pk_public = pubky::Keypair::from_secret(&pk_seed).public_key(); + assert_eq!(hex::encode(pk_public.to_bytes()), s(&v, "pk_public_key")); + assert_eq!(format!("pk:{}", pk_public.z32()), s(&v, "pk_identity")); + + // Both proofs, byte for byte. + let pk_proof = + wake_sign_pubky_proof(s(&v, "pk_seed").to_string(), registration.message.clone()).unwrap(); + assert_eq!(pk_proof, s(&v, "pk_proof")); + assert_eq!( + lightning_sign(&ln_secret, registration.message.as_bytes()), + s(&v, "ln_proof") + ); + + // The request body, proofs included, is the vector's request. + let proofs = vec![ + WakeIdentityProof { + identity: s(&v, "ln_identity").to_string(), + signature: s(&v, "ln_proof").to_string(), + }, + WakeIdentityProof { + identity: s(&v, "pk_identity").to_string(), + signature: pk_proof, + }, + ]; + let request = build_register(GATEWAY, ®istration, &proofs).unwrap(); + assert_eq!(request.method, Method::POST); + assert_eq!(request.url.as_str(), "https://wake.example/v1/devices"); + assert!(request.bearer.is_none()); + let body = request.body.unwrap(); + assert!(body.starts_with(r#"{"app":"bitkit","install_id":"AAAAAAAAAAAAAAAAAAAAAA","#)); + assert_eq!(serde_json::from_str::(&body).unwrap(), v["request"]); + + // Fields changed after the message was prepared are refused. + for tweak in 0..3 { + let mut changed = registration.clone(); + match tweak { + 0 => changed.topics.push("paykit.*".to_string()), + 1 => changed.audience = "wake.example".to_string(), + _ => changed.timestamp += 1, + } + assert_eq!( + build_register(GATEWAY, &changed, &proofs).err(), + Some(invalid_input( + "the registration changed after its message was prepared" + )) + ); + } + + // A different audience changes the message. + let other = vector_registration(&json!({"audience": "wake.example", "request": v["request"]})); + assert_ne!(other.message, registration.message); +} + +#[test] +fn legacy_registration_vector_checks_the_test_signer() { + let v = load(LEGACY_REGISTRATION); + let message = format!( + "bitkit-notifications{}{}", + s(&v, "device_token"), + s(&v, "iso_timestamp") + ); + assert_eq!(message, s(&v, "message")); + assert_eq!( + hex::encode(lightning_message_hash(message.as_bytes())), + s(&v, "message_hash") + ); + let node = secret(&v, "node_secret_key"); + assert_eq!(public_hex(&node), s(&v, "node_id")); + assert_eq!( + lightning_sign(&node, message.as_bytes()), + s(&v, "signature_zbase32") + ); +} + +// ----- (f) every push shape ----- + +#[test] +fn pushes_vector() { + let v = load(PUSHES); + let cases = v["cases"].as_array().unwrap(); + assert_eq!(cases.len(), 10); + for case in cases { + let name = s(case, "name"); + let body_text = s(case, "body"); + assert_eq!( + case["body_len"].as_u64().unwrap() as usize, + body_text.len(), + "{name}" + ); + let body: Value = serde_json::from_str(body_text).unwrap(); + let delivered = parse_delivered(body_text).unwrap(); + // An FCM device sees only the data map. + let mut views = vec![body_text.to_string()]; + if s(case, "provider") == "fcm" { + let message = &body["message"]; + let data = message.pointer("/android/data").unwrap_or(&message["data"]); + for map in [data, &message["data"]] { + assert_eq!( + parse_delivered(&map.to_string()).unwrap(), + delivered, + "{name}" + ); + views.push(map.to_string()); + } + } + match &delivered { + Delivered::V0(container) | Delivered::V1(container) => { + let scheme = if name.ends_with("v0") { + Scheme::V0 + } else { + Scheme::V1 + }; + assert_eq!(container.scheme(), scheme, "{name}"); + assert_eq!( + container.to_json(), + s(&case["inputs"], "container"), + "{name}" + ); + let device = secret(case, "device_secret_key"); + let plaintext = String::from_utf8(container.decrypt(&device).unwrap()).unwrap(); + assert_eq!(plaintext, s(case, "plaintext"), "{name}"); + for view in views { + let envelope = + wake_decrypt_push(s(case, "device_secret_key").to_string(), view.clone()) + .unwrap(); + check_envelope_matches_plaintext(&envelope, &plaintext, case, name); + } + } + Delivered::Fallback { id, topic } => { + assert!(case.get("device_secret_key").is_none(), "{name}"); + assert_eq!(id, s(&case["inputs"], "wake_id"), "{name}"); + assert_eq!(topic, s(&case["inputs"], "topic"), "{name}"); + for view in views { + // A fallback needs no key, so an unusable one is fine. + let envelope = wake_decrypt_push(String::new(), view).unwrap(); + assert_eq!( + envelope, + WakeEnvelope { + version: 1, + id: Some(id.clone()), + topic: topic.clone(), + urgency: None, + deadline: None, + payload_json: None, + created_at: None, + fallback: true, + }, + "{name}" + ); + } + } + } + } +} + +fn check_envelope_matches_plaintext( + envelope: &WakeEnvelope, + plaintext: &str, + case: &Value, + name: &str, +) { + let parsed: Value = serde_json::from_str(plaintext).unwrap(); + assert!(!envelope.fallback, "{name}"); + let payload = envelope.payload_json.as_deref().unwrap_or("null"); + assert_eq!( + serde_json::from_str::(payload).unwrap(), + parsed["payload"], + "{name}" + ); + if envelope.version == 0 { + let topic = format!("{}.{}", s(&parsed, "source"), s(&parsed, "type")); + assert_eq!(envelope.topic, topic, "{name}"); + assert_eq!( + envelope.created_at.as_deref(), + parsed["createdAt"].as_str(), + "{name}" + ); + assert!( + plaintext.contains(&format!("\"payload\":{payload},\"createdAt\"")), + "{name}" + ); + assert_eq!(envelope.id, None, "{name}"); + } else { + assert_eq!(envelope.version, 1, "{name}"); + assert_eq!(envelope.id.as_deref(), Some(s(&case["inputs"], "wake_id"))); + assert_eq!(envelope.topic, s(&case["inputs"], "topic"), "{name}"); + assert_eq!(envelope.urgency.as_deref(), parsed["urgency"].as_str()); + assert_eq!(envelope.deadline, parsed["deadline"].as_u64(), "{name}"); + assert!( + plaintext.ends_with(&format!("\"payload\":{payload}}}")), + "{name}" + ); + assert_eq!(envelope.created_at, None, "{name}"); + } +} + +// ----- Negative crypto cases ----- + +fn v1_vector_container() -> (SecretKey, Container) { + let v = load(V1_ENVELOPE); + ( + secret(&v, "device_secret_key"), + Container::from_json(s(&v, "container")).unwrap(), + ) +} + +#[test] +fn tampered_tag_fails() { + let (device, mut container) = v1_vector_container(); + container.tag_mut()[0] ^= 1; + assert_eq!(container.decrypt(&device), Err(decryption_failed())); + assert_eq!( + wake_decrypt_v1(hex::encode(device.secret_bytes()), container.to_json()), + Err(decryption_failed()) + ); +} + +#[test] +fn tampered_cipher_fails() { + let v = load(V0_SERVER); + let cipher = s(&v, "cipher").replacen('m', "n", 1); + let result = wake_decrypt_v0( + s(&v, "device_secret_key").to_string(), + cipher, + s(&v, "iv").to_string(), + s(&v, "tag").to_string(), + s(&v, "ephemeral_public_key").to_string(), + ); + assert_eq!(result, Err(decryption_failed())); +} + +#[test] +fn wrong_key_fails() { + let (_, container) = v1_vector_container(); + assert_eq!( + wake_decrypt_v1("23".repeat(32), container.to_json()), + Err(decryption_failed()) + ); +} + +#[test] +fn wrong_label_fails() { + // A v1 container read as a legacy one derives the key with the v0 label. + let v = load(V1_ENVELOPE); + let result = wake_decrypt_v0( + s(&v, "device_secret_key").to_string(), + s(&v, "cipher").to_string(), + s(&v, "iv").to_string(), + s(&v, "tag").to_string(), + s(&v, "ephemeral_public_key").to_string(), + ); + assert_eq!(result, Err(decryption_failed())); +} + +#[test] +fn eleven_byte_iv_is_rejected() { + let (device, container) = v1_vector_container(); + let json = container + .to_json() + .replace("000102030405060708090a0b", &"00".repeat(11)); + let iv_error = invalid_input("iv must be 12 or 16 bytes"); + assert_eq!(Container::from_json(&json), Err(iv_error.clone())); + assert_eq!( + wake_decrypt_v1(hex::encode(device.secret_bytes()), json), + Err(iv_error.clone()) + ); + let v = load(V0_SERVER); + let result = wake_decrypt_v0( + s(&v, "device_secret_key").to_string(), + s(&v, "cipher").to_string(), + "00".repeat(11), + s(&v, "tag").to_string(), + s(&v, "ephemeral_public_key").to_string(), + ); + assert_eq!(result, Err(iv_error)); +} + +#[test] +fn unknown_version_is_unsupported() { + let (device, container) = v1_vector_container(); + for other in ["2", "\"1\"", "null", "1.0", "0", "{}"] { + let json = container + .to_json() + .replacen("\"v\":1", &format!("\"v\":{other}"), 1); + assert_eq!( + Container::from_json(&json), + Err(unsupported("unsupported envelope version")), + "{other}" + ); + assert_eq!( + wake_decrypt_v1(hex::encode(device.secret_bytes()), json.clone()), + Err(unsupported("unsupported envelope version")), + "{other}" + ); + let push = format!("{{\"wake\":{json}}}"); + assert_eq!( + wake_decrypt_push(hex::encode(device.secret_bytes()), push), + Err(unsupported("unsupported envelope version")), + "{other}" + ); + } + // A container without `v` is a legacy one, which the v1 entry point refuses. + let v0 = load(V0_SERVER); + assert_eq!( + wake_decrypt_v1( + s(&v0, "device_secret_key").to_string(), + s(&v0, "container").to_string() + ), + Err(unsupported("expected a v1 container")) + ); +} + +#[test] +fn unsupported_plaintext_version_and_malformed_plaintexts() { + let v = load(V1_ENVELOPE); + let device = secret(&v, "device_secret_key"); + let seal = |plaintext: &str| { + seal_deterministic( + Scheme::V1, + &bytes33(s(&v, "device_public_key")), + plaintext.as_bytes(), + &secret(&v, "ephemeral_secret_key"), + &[7; 12], + ) + .open_envelope(&device) + }; + let v2 = s(&v, "plaintext").replacen("\"v\":1", "\"v\":2", 1); + assert_eq!(seal(&v2), Err(unsupported("unsupported plaintext version"))); + let secret_payload = "paymentHash=e3b0c44298fc1c14"; + for bad in [ + s(&v, "plaintext").replacen("\"background\"", "\"urgent\"", 1), + s(&v, "plaintext").replacen(WAKE_ID, "not-a-uuid", 1), + format!( + r#"{{"v":1,"id":"{WAKE_ID}","topic":"a.b","urgency":"alert","deadline":"{secret_payload}"}}"# + ), + ] { + let err = seal(&bad).unwrap_err(); + assert!(matches!(err, WakeError::InvalidInput { .. }), "{err:?}"); + assert!(!err.to_string().contains("e3b0c442"), "{err}"); + } + // A null or absent payload is no payload. + let null_payload = format!( + r#"{{"v":1,"id":"{WAKE_ID}","topic":"paykit.sync","urgency":"background","deadline":1,"payload":null}}"# + ); + assert_eq!(seal(&null_payload).unwrap().payload_json, None); + let absent = null_payload.replace(",\"payload\":null", ""); + assert_eq!(seal(&absent).unwrap().payload_json, None); +} + +#[test] +fn unpadded_base64_is_rejected() { + let v = load(V0_SERVER); + let json = s(&v, "container").replacen("O9h\"", "O9h=\"", 1); + assert!(Container::from_json(&json).is_err()); + let unpadded = load(V0_CLIENT); + let json = s(&unpadded, "container").replacen("A=\"", "A\"", 1); + assert_eq!( + Container::from_json(&json), + Err(invalid_input("cipher is not padded standard base64")) + ); +} + +#[test] +fn off_curve_ephemeral_key_fails_decryption() { + let (device, mut container) = v1_vector_container(); + let key = container.public_key_mut(); + key[0] = 0x02; + key[1..].copy_from_slice(&[0xff; 32]); + assert!(matches!( + container.decrypt(&device), + Err(WakeError::InvalidKey { .. }) + )); +} + +#[test] +fn malformed_secret_keys_are_invalid_keys() { + let (_, container) = v1_vector_container(); + for bad in [ + String::new(), + "zz".repeat(32), + "11".repeat(31), + "00".repeat(32), + "ff".repeat(32), + ] { + let result = wake_decrypt_v1(bad.clone(), container.to_json()); + assert!( + matches!(result, Err(WakeError::InvalidKey { .. })), + "{bad}: {result:?}" + ); + } +} + +// ----- Push shape detection ----- + +#[test] +fn parse_delivered_rejects_what_it_cannot_place() { + let v0 = load(V0_SERVER); + let v0 = s(&v0, "container"); + let v1 = load(V1_ENVELOPE); + let v1 = s(&v1, "container"); + let v2 = v1.replacen("\"v\":1", "\"v\":2", 1); + let misplaced = Err(unsupported( + "container version does not match where it was found", + )); + assert_eq!(parse_delivered(&format!("{{\"wake\":{v0}}}")), misplaced); + assert_eq!( + parse_delivered(&format!( + "{{\"wake\":{}}}", + serde_json::to_string(v0).unwrap() + )), + misplaced + ); + assert_eq!( + parse_delivered(&format!("{{\"aps\":{{\"alert\":{{\"payload\":{v1}}}}}}}")), + misplaced + ); + assert_eq!( + parse_delivered(&format!("{{\"wake\":{v2}}}")), + Err(unsupported("unsupported envelope version")) + ); + assert_eq!( + parse_delivered(&format!( + "{{\"wake\":{}}}", + serde_json::to_string(&v2).unwrap() + )), + Err(unsupported("unsupported envelope version")) + ); + assert_eq!( + parse_delivered(&format!( + r#"{{"wake_fallback":{{"v":2,"id":"{WAKE_ID}","topic":"a.b"}}}}"# + )), + Err(unsupported("unsupported envelope version")) + ); + assert!(matches!( + parse_delivered(r#"{"wake_fallback":"{\"v\":1,\"topic\":\"a.b\"}"}"#), + Err(WakeError::InvalidInput { .. }) + )); + assert_eq!( + parse_delivered(r#"{"wake_fallback":{"v":1,"id":"x","topic":"a.b"}}"#), + Err(invalid_input("wake id is not a uuid")) + ); + assert_eq!( + parse_delivered(r#"{"cipher":"AA==","iv":"00"}"#), + Err(invalid_input( + "legacy data needs cipher, iv, tag and publicKey strings" + )) + ); + assert_eq!( + parse_delivered(r#"{"wake":7}"#), + Err(invalid_input("wake is not a container")) + ); + assert_eq!( + parse_delivered(r#"{"aps":{"alert":{"payload":"x"}}}"#), + Err(invalid_input("aps.alert.payload is not a container")) + ); + assert_eq!( + parse_delivered(r#"{"aps":{"content-available":1}}"#), + Err(unsupported("push carries no wake")) + ); + assert_eq!( + parse_delivered(r#"{"message":{"token":"t"}}"#), + Err(unsupported("FCM message carries no data")) + ); + assert_eq!( + parse_delivered("[]"), + Err(invalid_input("push is not a JSON object")) + ); + assert!(matches!( + parse_delivered("{"), + Err(WakeError::InvalidInput { .. }) + )); + // The legacy FCM data map's string `message` is alert text, not a body. + let flat: Value = serde_json::from_str(v0).unwrap(); + let mut flat = flat.as_object().unwrap().clone(); + flat.insert("message".to_string(), json!("Please open your wallet.")); + assert!(matches!( + parse_delivered(&Value::Object(flat).to_string()), + Ok(Delivered::V0(_)) + )); +} + +#[test] +fn fallback_markers_in_both_encodings() { + let marker = json!({"v": 1, "id": WAKE_ID.to_uppercase(), "topic": "blocktank.incomingHtlc"}); + let expected = Delivered::Fallback { + id: WAKE_ID.to_string(), + topic: "blocktank.incomingHtlc".to_string(), + }; + let apns = json!({"aps": {"alert": {"title": "t", "body": "b"}}, "wake_fallback": marker}); + let fcm = json!({"wake_fallback": marker.to_string()}); + assert_eq!(parse_delivered(&apns.to_string()).unwrap(), expected); + assert_eq!(parse_delivered(&fcm.to_string()).unwrap(), expected); +} + +// ----- Registration ----- + +fn fields<'a>(identities: &'a [String], topics: &'a [String]) -> RegistrationFields<'a> { + RegistrationFields { + audience: "wake.localhost", + app: "bitkit", + install_id: "AAAAAAAAAAAAAAAAAAAAAA", + platform: WakePlatform::Apns, + environment: WakeEnvironment::Sandbox, + push_token: "00ff", + encryption_key: "02aa", + secret_sha256: "bb", + identities, + topics, + ts: 1790000000, + } +} + +#[test] +fn preimage_sorts_and_dedupes() { + let ids = vec!["pk:b".to_string(), "ln:a".to_string(), "pk:b".to_string()]; + let topics = vec!["paykit.sync".to_string(), "blocktank.*".to_string()]; + let pre = fields(&ids, &topics).preimage().unwrap(); + assert_eq!( + pre, + "wake-register-v1\naud:wake.localhost\napp:bitkit\ninstall:AAAAAAAAAAAAAAAAAAAAAA\n\ + platform:apns\nenvironment:sandbox\npush_token:00ff\nencryption_key:02aa\n\ + secret_sha256:bb\nidentities:ln:a,pk:b\ntopics:blocktank.*,paykit.sync\nts:1790000000" + ); + assert_eq!(pre.lines().count(), 12); + assert!(!pre.ends_with('\n')); +} + +#[test] +fn empty_topics_give_a_bare_label() { + let ids = vec!["ln:a".to_string()]; + let pre = fields(&ids, &[]).preimage().unwrap(); + assert!(pre.contains("\ntopics:\nts:")); +} + +#[test] +fn ambiguous_preimage_fields_are_rejected() { + let ids = vec!["ln:a".to_string()]; + let comma = vec!["a.b,c.d".to_string()]; + let list_error = Err(invalid_input( + "a list entry is empty or contains a comma or newline", + )); + assert_eq!(fields(&ids, &comma).preimage(), list_error); + let newline_topic = vec!["a.b\nc.d".to_string()]; + assert_eq!(fields(&ids, &newline_topic).preimage(), list_error); + for field in 0..6 { + let mut f = fields(&ids, &[]); + let slot = match field { + 0 => &mut f.audience, + 1 => &mut f.app, + 2 => &mut f.install_id, + 3 => &mut f.push_token, + 4 => &mut f.encryption_key, + _ => &mut f.secret_sha256, + }; + *slot = "ab\ncd"; + assert_eq!( + f.preimage(), + Err(invalid_input("a registration field contains a newline")), + "{field}" + ); + } +} + +#[test] +fn empty_list_entries_are_rejected() { + let list_error = Err(invalid_input( + "a list entry is empty or contains a comma or newline", + )); + let ids = vec!["ln:a".to_string()]; + assert_eq!(fields(&ids, &[String::new()]).preimage(), list_error); + let with_empty = vec!["a.b".to_string(), String::new()]; + assert_eq!(fields(&ids, &with_empty).preimage(), list_error); + assert_eq!(fields(&[String::new()], &[]).preimage(), list_error); + assert!(fields(&[], &[]).preimage().is_ok()); +} + +/// Changes one argument of a registration. +type Tweak = Box; + +/// The vector's registration arguments, for tweaking one at a time. +struct Args { + audience: String, + app: String, + install_id: String, + platform: WakePlatform, + environment: WakeEnvironment, + push_token: String, + encryption_key: String, + secret_sha256: String, + identities: Vec, + topics: Vec, +} + +impl Args { + fn from_vector() -> Self { + let r = vector_registration(&load(REGISTRATION_V1)); + Args { + audience: r.audience, + app: r.app, + install_id: r.install_id, + platform: r.platform, + environment: r.environment, + push_token: r.push_token, + encryption_key: r.encryption_public_key, + secret_sha256: r.secret_sha256, + identities: r.identities, + topics: r.topics, + } + } + + fn prepare(self) -> Result { + wake_prepare_registration( + self.audience, + self.app, + self.install_id, + self.platform, + self.environment, + self.push_token, + self.encryption_key, + self.secret_sha256, + self.identities, + self.topics, + Some(1790000000), + ) + } +} + +#[test] +fn prepare_rejects_what_the_gateway_would() { + let ln = load(REGISTRATION_V1)["ln_identity"] + .as_str() + .unwrap() + .to_string(); + let pk = load(REGISTRATION_V1)["pk_identity"] + .as_str() + .unwrap() + .to_string(); + let other_ln = format!( + "ln:{}", + load(V0_SERVER)["device_public_key"].as_str().unwrap() + ); + let mut non_canonical_pk = pk.clone(); + // The last character carries 1 data bit and 4 padding bits. + assert!(non_canonical_pk.ends_with('y')); + non_canonical_pk.pop(); + non_canonical_pk.push('b'); + let cases: Vec<(&str, Tweak)> = vec![ + ( + "empty audience", + Box::new(|a: &mut Args| a.audience.clear()), + ), + ( + "short install id", + Box::new(|a: &mut Args| a.install_id = "A".repeat(15)), + ), + ( + "padded install id", + Box::new(|a: &mut Args| a.install_id.push('=')), + ), + ( + "fcm sandbox", + Box::new(|a: &mut Args| { + a.platform = WakePlatform::Fcm; + a.push_token = "a:b_c-d".repeat(15); + }), + ), + ( + "uppercase apns token", + Box::new(|a: &mut Args| a.push_token = a.push_token.to_uppercase()), + ), + ( + "odd apns token", + Box::new(|a: &mut Args| a.push_token.push('0')), + ), + ( + "short fcm token", + Box::new(|a: &mut Args| { + a.platform = WakePlatform::Fcm; + a.environment = WakeEnvironment::Production; + a.push_token = "a".repeat(99); + }), + ), + ( + "uppercase key", + Box::new(|a: &mut Args| a.encryption_key = a.encryption_key.to_uppercase()), + ), + ( + "off-curve key", + Box::new(|a: &mut Args| a.encryption_key = format!("02{}", "ff".repeat(32))), + ), + ( + "uppercase secret hash", + Box::new(|a: &mut Args| a.secret_sha256 = a.secret_sha256.to_uppercase()), + ), + ("no identity", Box::new(|a: &mut Args| a.identities.clear())), + ("two ln", { + let (ln, other_ln) = (ln.clone(), other_ln.clone()); + Box::new(move |a: &mut Args| a.identities = vec![ln.clone(), other_ln.clone()]) + }), + ("duplicate", { + let pk = pk.clone(); + Box::new(move |a: &mut Args| a.identities = vec![pk.clone(), pk.clone()]) + }), + ("three", { + let (ln, pk, other_ln) = (ln.clone(), pk.clone(), other_ln.clone()); + Box::new(move |a: &mut Args| { + a.identities = vec![ln.clone(), pk.clone(), other_ln.clone()] + }) + }), + ("pubky prefix", { + let pk = pk.clone(); + Box::new(move |a: &mut Args| a.identities = vec![pk.replacen("pk:", "pk:pubky", 1)]) + }), + ("uppercase pk", { + let pk = pk.clone(); + Box::new(move |a: &mut Args| { + a.identities = vec![format!("pk:{}", pk[3..].to_uppercase())] + }) + }), + ("non-canonical pk", { + let pk = non_canonical_pk.clone(); + Box::new(move |a: &mut Args| a.identities = vec![pk.clone()]) + }), + ( + "weak pk", + Box::new(|a: &mut Args| a.identities = vec![format!("pk:{}", "y".repeat(52))]), + ), + ("uppercase ln", { + let ln = ln.clone(); + Box::new(move |a: &mut Args| { + a.identities = vec![format!("ln:{}", ln[3..].to_uppercase())] + }) + }), + ( + "off-curve ln", + Box::new(|a: &mut Args| a.identities = vec![format!("ln:02{}", "ff".repeat(32))]), + ), + ("bare key", { + let ln = ln.clone(); + Box::new(move |a: &mut Args| a.identities = vec![ln[3..].to_string()]) + }), + ( + "33 topics", + Box::new(|a: &mut Args| a.topics = (0..33).map(|i| format!("t.t{i}")).collect()), + ), + ( + "no dot", + Box::new(|a: &mut Args| a.topics = vec!["blocktank".to_string()]), + ), + ( + "uppercase namespace", + Box::new(|a: &mut Args| a.topics = vec!["Blocktank.x".to_string()]), + ), + ( + "bare star", + Box::new(|a: &mut Args| a.topics = vec!["*".to_string()]), + ), + ( + "long", + Box::new(|a: &mut Args| a.topics = vec![format!("a.{}", "b".repeat(63))]), + ), + ]; + for (name, tweak) in cases { + let mut args = Args::from_vector(); + tweak(&mut args); + let result = args.prepare(); + assert!( + matches!( + result, + Err(WakeError::InvalidInput { .. } | WakeError::InvalidKey { .. }) + ), + "{name}: {result:?}" + ); + } + + let mut fcm = Args::from_vector(); + fcm.platform = WakePlatform::Fcm; + fcm.environment = WakeEnvironment::Production; + fcm.push_token = "a:b_c-d".repeat(15); + fcm.identities = vec![pk.clone()]; + fcm.topics = vec!["blocktank.incomingHtlc".to_string(), "paykit.*".to_string()]; + let prepared = fcm.prepare().unwrap(); + assert!(prepared + .message + .contains("\nplatform:fcm\nenvironment:production\n")); + assert!(prepared.message.contains(&format!("\nidentities:{pk}\n"))); + let mut no_topics = Args::from_vector(); + no_topics.topics.clear(); + assert!(no_topics.prepare().is_ok()); +} + +#[test] +fn prepare_defaults_the_timestamp_to_now() { + let v = load(REGISTRATION_V1); + let mut request = v["request"].clone(); + request["ts"] = Value::Null; + let prepared = vector_registration(&json!({"audience": v["audience"], "request": request})); + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + assert!(prepared.timestamp.abs_diff(now) <= 5); + assert!(prepared + .message + .ends_with(&format!("\nts:{}", prepared.timestamp))); +} + +#[test] +fn pubky_proofs_only_sign_registration_messages() { + let v = load(REGISTRATION_V1); + for message in ["", "wake-register-v1", "wake-register-v1x\n", "hello"] { + assert_eq!( + wake_sign_pubky_proof(s(&v, "pk_seed").to_string(), message.to_string()), + Err(invalid_input( + "only wake registration messages can be signed" + )), + "{message:?}" + ); + } + for key in ["", "44", &"zz".repeat(32)] { + assert!(matches!( + wake_sign_pubky_proof(key.to_string(), s(&v, "preimage").to_string()), + Err(WakeError::InvalidKey { .. }) + )); + } + let signature = wake_sign_pubky_proof( + s(&v, "pk_seed").to_string(), + "wake-register-v1\n".to_string(), + ) + .unwrap(); + assert_eq!(signature.len(), 128); +} + +// ----- Credentials and keys ----- + +#[test] +fn device_secret_format() { + let secret = device_secret_from_bytes(&[0x77; 32]); + assert_eq!(secret.token, DEVICE_SECRET); + assert_eq!( + secret.sha256_hex, + "a4908532a2f65068ce0b3498af2b68e43949be33aa85840288e653295bf52080" + ); + assert!(is_valid_device_secret(&secret.token)); + assert!(!format!("{secret:?}").contains("d3d3")); + + let generated = wake_generate_device_secret().unwrap(); + assert!(is_valid_device_secret(&generated.token)); + assert_ne!(generated.token, secret.token); + assert_eq!( + generated.sha256_hex, + hex::encode(sha256::Hash::hash(generated.token.as_bytes()).to_byte_array()) + ); +} + +#[test] +fn device_secret_rejects_malformed() { + let body = &DEVICE_SECRET[4..]; + for bad in [ + format!("wkp_{body}"), + body.to_string(), + format!("wkd_{}", &body[..42]), + format!("wkd_{body}A"), + format!("wkd_{}d", &body[..42]), + format!("wkd_{}=", &body[..42]), + format!("wkd_{}+", &body[..42]), + ] { + assert!(!is_valid_device_secret(&bad), "{bad}"); + } +} + +#[test] +fn install_ids_are_22_base64url_characters() { + assert_eq!(install_id_from_bytes(&[0xfb; 16]), "-_v7-_v7-_v7-_v7-_v7-w"); + let generated = wake_generate_install_id(); + assert_eq!(generated.len(), 22); + assert!(generated + .bytes() + .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')); + assert_ne!(generated, wake_generate_install_id()); +} + +#[test] +fn keypair_generation_retries_out_of_range_draws() { + // Zero and n..2^256 are not scalars; the third draw is. + let mut draws = 0; + let keypair = generate_keypair_with(|buf: &mut [u8]| { + draws += 1; + buf.fill(match draws { + 1 => 0, + 2 => 0xff, + _ => 0x11, + }); + Ok(()) + }) + .unwrap(); + assert_eq!(keypair.secret_key_hex, "11".repeat(32)); + assert_eq!( + keypair.public_key_hex, + s(&load(V0_SERVER), "ephemeral_public_key") + ); + assert!(!format!("{keypair:?}").contains(&"11".repeat(32))); +} + +#[test] +fn constant_invalid_entropy_fails_instead_of_hanging() { + for byte in [0x00, 0xff] { + let mut draws = 0; + let result = generate_keypair_with(|buf: &mut [u8]| { + draws += 1; + buf.fill(byte); + Ok(()) + }); + assert_eq!( + result, + Err(WakeError::InvalidKey { + reason: "entropy source is degenerate".to_string() + }) + ); + assert_eq!(draws, 64); + } +} + +#[test] +fn generated_keypairs_decrypt_what_is_sealed_to_them() { + let keypair = wake_generate_keypair().unwrap(); + assert_eq!(keypair.secret_key_hex.len(), 64); + assert_eq!(keypair.public_key_hex.len(), 66); + assert_ne!(keypair, wake_generate_keypair().unwrap()); + let plaintext = format!( + r#"{{"v":1,"id":"{WAKE_ID}","topic":"a.b","urgency":"alert","deadline":9,"payload":[1]}}"# + ); + let container = seal_deterministic( + Scheme::V1, + &bytes33(&keypair.public_key_hex), + plaintext.as_bytes(), + &parse_secret_key(&"66".repeat(32)).unwrap(), + &[1; 12], + ); + let envelope = wake_decrypt_v1(keypair.secret_key_hex, container.to_json()).unwrap(); + assert_eq!(envelope.payload_json.as_deref(), Some("[1]")); +} + +// ----- Client: builders ----- + +fn body_json(request: &HttpRequest) -> Value { + serde_json::from_str(request.body.as_deref().unwrap()).unwrap() +} + +#[test] +fn builders_for_every_route() { + let topics = vec!["blocktank.*".to_string()]; + let routes = [ + ( + build_server_info(GATEWAY).unwrap(), + Method::GET, + "v1/info", + false, + None, + ), + ( + build_list_topics(GATEWAY).unwrap(), + Method::GET, + "v1/topics", + false, + None, + ), + ( + build_ack(GATEWAY, DEVICE_SECRET, WAKE_ID, WakeAckOutcome::NoHandler).unwrap(), + Method::POST, + "v1/acks", + true, + Some(json!({"id": WAKE_ID, "outcome": "no_handler"})), + ), + ( + build_set_presence(GATEWAY, DEVICE_SECRET, 60).unwrap(), + Method::PUT, + "v1/presence", + true, + Some(json!({"ttl_secs": 60})), + ), + ( + build_clear_presence(GATEWAY, DEVICE_SECRET).unwrap(), + Method::DELETE, + "v1/presence", + true, + None, + ), + ( + build_set_topics(GATEWAY, DEVICE_SECRET, &topics).unwrap(), + Method::PUT, + "v1/devices/self/topics", + true, + Some(json!({"topics": ["blocktank.*"]})), + ), + ( + build_unregister(GATEWAY, DEVICE_SECRET).unwrap(), + Method::DELETE, + "v1/devices/self", + true, + None, + ), + ]; + for (request, method, route, authenticated, body) in routes { + assert_eq!(request.method, method, "{route}"); + assert_eq!(request.url.as_str(), format!("{GATEWAY}/{route}")); + assert_eq!( + request.bearer.as_deref(), + authenticated.then_some(DEVICE_SECRET), + "{route}" + ); + match body { + Some(expected) => assert_eq!(body_json(&request), expected, "{route}"), + None => assert!(request.body.is_none(), "{route}"), + } + } + for (outcome, wire) in [ + (WakeAckOutcome::Handled, "handled"), + (WakeAckOutcome::NoHandler, "no_handler"), + (WakeAckOutcome::Failed, "failed"), + ] { + let request = build_ack(GATEWAY, DEVICE_SECRET, WAKE_ID, outcome).unwrap(); + assert_eq!(body_json(&request)["outcome"], wire); + } +} + +#[test] +fn gateway_urls_keep_their_path_prefix() { + for (base, expected) in [ + ("https://wake.example", "https://wake.example/v1/info"), + ("https://wake.example/", "https://wake.example/v1/info"), + ( + "https://api.example/wake", + "https://api.example/wake/v1/info", + ), + ( + "https://api.example/wake/", + "https://api.example/wake/v1/info", + ), + ( + "http://127.0.0.1:9010?x=1#y", + "http://127.0.0.1:9010/v1/info", + ), + ] { + assert_eq!(build_server_info(base).unwrap().url.as_str(), expected); + } + for bad in ["", "wake.example", "ftp://wake.example", "file:///tmp/x"] { + assert!(matches!( + build_server_info(bad), + Err(WakeError::InvalidInput { .. }) + )); + } +} + +#[test] +fn builders_validate_secrets_and_wake_ids() { + let malformed = "malformed device secret"; + for bad in ["", "wkd_", "Bearer x", &format!("{DEVICE_SECRET}\r\nX: y")] { + assert_eq!( + build_clear_presence(GATEWAY, bad).err(), + Some(invalid_input(malformed)) + ); + } + assert_eq!( + build_ack(GATEWAY, DEVICE_SECRET, "nope", WakeAckOutcome::Handled).err(), + Some(invalid_input("wake id is not a uuid")) + ); + let upper = build_ack( + GATEWAY, + DEVICE_SECRET, + &WAKE_ID.to_uppercase(), + WakeAckOutcome::Handled, + ) + .unwrap(); + assert_eq!(body_json(&upper)["id"], WAKE_ID); +} + +// ----- Client: parsers ----- + +fn response(status: u16, body: &str) -> HttpResponse { + HttpResponse { + status, + body: body.to_string(), + } +} + +fn rejected(status: u16, code: &str, detail: &str) -> WakeError { + WakeError::GatewayRejected { + status, + code: code.to_string(), + detail: detail.to_string(), + } +} + +#[test] +fn parsers_for_every_route() { + assert_eq!( + parse_server_info(&response( + 200, + r#"{"audience":"wake.localhost","server_time":1790000000,"version":"0.1.0"}"# + )) + .unwrap(), + WakeServerInfo { + audience: "wake.localhost".to_string(), + server_time: 1790000000, + version: "0.1.0".to_string(), + } + ); + let registered = r#"{"device_id":"01a0c450-6c00-7000-8000-0000000000aa","identities":["ln:02"],"topics":["blocktank.*"],"unknown_topics":["x.y"],"server_time":5}"#; + for status in [200, 201] { + assert_eq!( + parse_register(&response(status, registered)).unwrap(), + WakeRegistration { + device_id: "01a0c450-6c00-7000-8000-0000000000aa".to_string(), + identities: vec!["ln:02".to_string()], + topics: vec!["blocktank.*".to_string()], + unknown_topics: vec!["x.y".to_string()], + server_time: 5, + } + ); + } + assert_eq!( + parse_set_presence(&response(200, r#"{"expires_at":1790000060}"#)).unwrap(), + 1790000060 + ); + assert_eq!( + parse_set_topics(&response( + 200, + r#"{"topics":["blocktank.*"],"unknown_topics":["x.y"]}"# + )) + .unwrap(), + vec!["blocktank.*".to_string()] + ); + assert_eq!(parse_no_content(&response(204, "")), Ok(())); + + let topics = r#"{"topics":[ + {"name":"blocktank.incomingHtlc","description":"An LSP holds an HTLC","owner":"blocktank", + "apps":["bitkit"],"urgency":"background","deadline_secs":60,"presence":"ignore","peer":false, + "alertable":true,"display":{"title":"Incoming Payment","body":"Open Bitkit"}, + "fallback":{"window_secs":45},"max_payload_bytes":512}, + {"name":"paykit.sync","description":"d","owner":"peer","apps":["bitkit"],"urgency":"background", + "deadline_secs":600,"presence":"only_if_offline","peer":true,"alertable":false,"display":null, + "fallback":null,"max_payload_bytes":0}]}"#; + assert_eq!( + parse_list_topics(&response(200, topics)).unwrap(), + vec![ + WakeTopic { + name: "blocktank.incomingHtlc".to_string(), + description: "An LSP holds an HTLC".to_string(), + urgency: "background".to_string(), + deadline_secs: 60, + alertable: true, + title: Some("Incoming Payment".to_string()), + body: Some("Open Bitkit".to_string()), + peer: false, + }, + WakeTopic { + name: "paykit.sync".to_string(), + description: "d".to_string(), + urgency: "background".to_string(), + deadline_secs: 600, + alertable: false, + title: None, + body: None, + peer: true, + }, + ] + ); +} + +#[test] +fn error_statuses_map_to_gateway_rejected() { + let unauthorized = response(401, r#"{"error":"unauthorized","message":"bad secret"}"#); + let unknown_wake = response(404, r#"{"error":"unknown_wake","message":"no delivery"}"#); + let stale = response( + 409, + r#"{"error":"stale_registration","message":"older than the stored one"}"#, + ); + let stale_ts = response( + 401, + r#"{"error":"stale_timestamp","message":"skew","server_time":1790000000}"#, + ); + assert_eq!( + parse_no_content(&unauthorized), + Err(rejected(401, "unauthorized", "bad secret")) + ); + assert_eq!( + parse_no_content(&unknown_wake), + Err(rejected(404, "unknown_wake", "no delivery")) + ); + assert_eq!( + parse_register(&stale), + Err(rejected( + 409, + "stale_registration", + "older than the stored one" + )) + ); + assert_eq!( + parse_register(&stale_ts), + Err(rejected(401, "stale_timestamp", "skew")) + ); + assert_eq!( + parse_set_presence(&unauthorized), + Err(rejected(401, "unauthorized", "bad secret")) + ); + assert_eq!( + parse_set_topics(&unauthorized), + Err(rejected(401, "unauthorized", "bad secret")) + ); + assert_eq!( + parse_server_info(&response(503, r#"{"error":"unavailable","message":"db"}"#)), + Err(rejected(503, "unavailable", "db")) + ); + let no_body = rejected(502, "unknown", "the response carries no wake error body"); + for body in ["Bad Gateway", "", r#"{"error":7}"#] { + assert_eq!( + parse_list_topics(&response(502, body)), + Err(no_body.clone()) + ); + } + assert_eq!( + parse_no_content(&response(302, "")), + Err(rejected( + 302, + "unknown", + "the response carries no wake error body" + )) + ); +} + +#[test] +fn malformed_success_bodies_are_invalid_responses() { + for body in [ + "", + "", + r#"{"audience":"a"}"#, + r#"{"expires_at":"soon"}"#, + ] { + assert!(matches!( + parse_server_info(&response(200, body)), + Err(WakeError::InvalidResponse { .. }) + )); + let err = parse_set_presence(&response(200, body)).unwrap_err(); + assert!(matches!(err, WakeError::InvalidResponse { .. })); + assert!(!err.to_string().contains("soon"), "{err}"); + } +} + +// ----- Client: executor, against a local server ----- + +/// Serves one canned response on 127.0.0.1 and returns what it received. +async fn serve_once(status: u16, body: &'static str) -> (String, tokio::task::JoinHandle) { + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let url = format!("http://{}/prefix", listener.local_addr().unwrap()); + let handle = tokio::spawn(async move { + let (mut socket, _) = listener.accept().await.unwrap(); + let mut received = Vec::new(); + let mut chunk = [0u8; 4096]; + loop { + let n = socket.read(&mut chunk).await.unwrap(); + received.extend_from_slice(&chunk[..n]); + let text = String::from_utf8_lossy(&received).to_string(); + if let Some(end) = text.find("\r\n\r\n") { + let length = text[..end] + .lines() + .find_map(|l| { + l.to_ascii_lowercase() + .strip_prefix("content-length: ") + .map(|v| v.trim().parse::().unwrap()) + }) + .unwrap_or(0); + if received.len() >= end + 4 + length || n == 0 { + break; + } + } + } + let location = if (300..400).contains(&status) { + "Location: /moved\r\n" + } else { + "" + }; + let reply = format!( + "HTTP/1.1 {status} X\r\n{location}Content-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() + ); + socket.write_all(reply.as_bytes()).await.unwrap(); + String::from_utf8(received).unwrap() + }); + (url, handle) +} + +#[tokio::test] +async fn executor_sends_the_built_request() { + let (url, server) = serve_once(200, r#"{"expires_at":1790000060}"#).await; + let expires_at = wake_set_presence(url, DEVICE_SECRET.to_string(), 90) + .await + .unwrap(); + assert_eq!(expires_at, 1790000060); + let received = server.await.unwrap(); + let lower = received.to_ascii_lowercase(); + assert!( + received.starts_with("PUT /prefix/v1/presence HTTP/1.1\r\n"), + "{received}" + ); + assert!(lower.contains(&format!( + "authorization: bearer {}", + DEVICE_SECRET.to_ascii_lowercase() + ))); + assert!(lower.contains("content-type: application/json")); + assert!(lower.contains("accept: application/json")); + assert!( + received.ends_with("\r\n\r\n{\"ttl_secs\":90}"), + "{received}" + ); +} + +#[tokio::test] +async fn executor_maps_statuses_and_network_failures() { + let (url, server) = serve_once(404, r#"{"error":"unknown_wake","message":"m"}"#).await; + let result = wake_ack( + url, + DEVICE_SECRET.to_string(), + WAKE_ID.to_string(), + WakeAckOutcome::Handled, + ) + .await; + assert_eq!(result, Err(rejected(404, "unknown_wake", "m"))); + let received = server.await.unwrap(); + assert!(received.starts_with("POST /prefix/v1/acks HTTP/1.1\r\n")); + assert!(received.ends_with(&format!("{{\"id\":\"{WAKE_ID}\",\"outcome\":\"handled\"}}"))); + + let (url, server) = serve_once(204, "").await; + assert_eq!( + wake_unregister(url, DEVICE_SECRET.to_string()).await, + Ok(()) + ); + assert!(server + .await + .unwrap() + .starts_with("DELETE /prefix/v1/devices/self HTTP/1.1\r\n")); + + // Redirects are not followed. + let (url, server) = serve_once(307, "").await; + assert_eq!( + wake_clear_presence(url, DEVICE_SECRET.to_string()).await, + Err(rejected( + 307, + "unknown", + "the response carries no wake error body" + )) + ); + server.await.unwrap(); + + // Nothing listens on a port that was just released. + let port = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .unwrap() + .local_addr() + .unwrap() + .port(); + let result = wake_server_info(format!("http://127.0.0.1:{port}")).await; + assert!( + matches!(result, Err(WakeError::RequestFailed { .. })), + "{result:?}" + ); +} + +// ----- Live gateway ----- + +/// Registers against a running gateway with both proofs, then exercises +/// presence and topics. Run with +/// `WAKE_GATEWAY_URL=http://127.0.0.1:9010 cargo test wake -- --ignored`. +#[tokio::test] +#[ignore] +async fn live_gateway_round_trip() { + let url = std::env::var("WAKE_GATEWAY_URL").expect("WAKE_GATEWAY_URL is not set"); + let info = wake_server_info(url.clone()).await.unwrap(); + + let encryption = wake_generate_keypair().unwrap(); + let device_secret = wake_generate_device_secret().unwrap(); + let ln_secret = parse_secret_key(&wake_generate_keypair().unwrap().secret_key_hex).unwrap(); + let ln_identity = format!("ln:{}", public_hex(&ln_secret)); + let pk_secret_hex = wake_generate_keypair().unwrap().secret_key_hex; + let pk_seed: [u8; 32] = hex::decode(&pk_secret_hex).unwrap().try_into().unwrap(); + let pk_identity = format!( + "pk:{}", + pubky::Keypair::from_secret(&pk_seed).public_key().z32() + ); + let mut push_token = [0u8; 32]; + os_random(&mut push_token).unwrap(); + + let request = wake_prepare_registration( + info.audience.clone(), + "bitkit".to_string(), + wake_generate_install_id(), + WakePlatform::Apns, + WakeEnvironment::Sandbox, + hex::encode(push_token), + encryption.public_key_hex.clone(), + device_secret.sha256_hex.clone(), + vec![ln_identity.clone(), pk_identity.clone()], + vec!["blocktank.*".to_string()], + None, + ) + .unwrap(); + let proofs = vec![ + WakeIdentityProof { + identity: ln_identity.clone(), + signature: lightning_sign(&ln_secret, request.message.as_bytes()), + }, + WakeIdentityProof { + identity: pk_identity.clone(), + signature: wake_sign_pubky_proof(pk_secret_hex, request.message.clone()).unwrap(), + }, + ]; + let registration = wake_register(url.clone(), request, proofs).await.unwrap(); + let mut identities = registration.identities.clone(); + identities.sort(); + let mut expected = vec![ln_identity, pk_identity]; + expected.sort(); + assert_eq!(identities, expected); + assert_eq!(registration.topics, vec!["blocktank.*".to_string()]); + + let token = device_secret.token.clone(); + let expires_at = wake_set_presence(url.clone(), token.clone(), 60) + .await + .unwrap(); + assert!(expires_at > info.server_time); + wake_clear_presence(url.clone(), token.clone()) + .await + .unwrap(); + + let topics = wake_list_topics(url.clone()).await.unwrap(); + assert!(!topics.is_empty()); + let stored = wake_set_topics( + url.clone(), + token.clone(), + vec!["blocktank.*".to_string(), "unknown.topicName".to_string()], + ) + .await + .unwrap(); + assert_eq!(stored, vec!["blocktank.*".to_string()]); + + wake_unregister(url.clone(), token.clone()).await.unwrap(); + assert!(matches!( + wake_set_presence(url, token, 60).await, + Err(WakeError::GatewayRejected { status: 401, .. }) + )); +} diff --git a/src/modules/wake/types.rs b/src/modules/wake/types.rs new file mode 100644 index 0000000..968a86d --- /dev/null +++ b/src/modules/wake/types.rs @@ -0,0 +1,187 @@ +use std::fmt; + +/// A secp256k1 key pair for push encryption. The public key is registered +/// with the gateway; the secret key decrypts wakes and never leaves the device. +#[derive(uniffi::Record, Clone, PartialEq, Eq)] +pub struct WakeKeyPair { + /// 64 lowercase hex characters. + pub secret_key_hex: String, + /// Compressed public key, 66 lowercase hex characters. + pub public_key_hex: String, +} + +impl fmt::Debug for WakeKeyPair { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WakeKeyPair") + .field("secret_key_hex", &"") + .field("public_key_hex", &self.public_key_hex) + .finish() + } +} + +/// The bearer credential of the device routes, generated on the device. +#[derive(uniffi::Record, Clone, PartialEq, Eq)] +pub struct WakeDeviceSecret { + /// `wkd_` followed by 43 base64url characters. Keep it in secure storage + /// the notification extension can read. + pub token: String, + /// Lowercase hex SHA-256 of `token`, sent as `secret_sha256` when + /// registering. The gateway stores only this hash. + pub sha256_hex: String, +} + +impl fmt::Debug for WakeDeviceSecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WakeDeviceSecret") + .field("token", &"wkd_") + .field("sha256_hex", &self.sha256_hex) + .finish() + } +} + +/// Push transport of a device. +#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq)] +pub enum WakePlatform { + Apns, + Fcm, +} + +impl WakePlatform { + pub(crate) const fn as_str(self) -> &'static str { + match self { + WakePlatform::Apns => "apns", + WakePlatform::Fcm => "fcm", + } + } +} + +/// APNs environment. FCM devices are always `Production`. +#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq)] +pub enum WakeEnvironment { + Production, + Sandbox, +} + +impl WakeEnvironment { + pub(crate) const fn as_str(self) -> &'static str { + match self { + WakeEnvironment::Production => "production", + WakeEnvironment::Sandbox => "sandbox", + } + } +} + +/// What the device did with a wake. +#[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq)] +pub enum WakeAckOutcome { + /// The wake was handled; the gateway cancels its fallback alert. + Handled, + /// The app has no handler for the topic; the fallback alert still fires. + NoHandler, + /// Handling failed; the fallback alert still fires. + Failed, +} + +impl WakeAckOutcome { + pub(crate) const fn as_str(self) -> &'static str { + match self { + WakeAckOutcome::Handled => "handled", + WakeAckOutcome::NoHandler => "no_handler", + WakeAckOutcome::Failed => "failed", + } + } +} + +/// A registration ready to be signed. Every identity signs `message`: `ln:` +/// through the Lightning node's message signing (ldk-node `signMessage`), +/// `pk:` through `wake_sign_pubky_proof`. +#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq)] +pub struct WakeRegistrationRequest { + /// The gateway's audience, from `wake_server_info`. + pub audience: String, + pub app: String, + pub install_id: String, + pub platform: WakePlatform, + pub environment: WakeEnvironment, + pub push_token: String, + /// Compressed secp256k1 public key, 66 lowercase hex characters. + pub encryption_public_key: String, + /// `WakeDeviceSecret.sha256_hex`. + pub secret_sha256: String, + /// One `ln:` and/or one `pk:` identity. + pub identities: Vec, + /// Topic names and `.*` patterns. + pub topics: Vec, + /// Unix seconds. + pub timestamp: u64, + /// The exact UTF-8 text every identity signs. + pub message: String, +} + +/// One identity's signature over `WakeRegistrationRequest.message`. +#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq)] +pub struct WakeIdentityProof { + pub identity: String, + /// `ln:`: 104 zbase32 characters. `pk:`: 128 lowercase hex characters. + pub signature: String, +} + +/// The gateway's view of a registered device. +#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq)] +pub struct WakeRegistration { + pub device_id: String, + pub identities: Vec, + /// Stored topics: the known names and every pattern. + pub topics: Vec, + /// Exact names the gateway does not know. They are not stored. + pub unknown_topics: Vec, + pub server_time: u64, +} + +/// `GET /v1/info`. +#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq)] +pub struct WakeServerInfo { + /// The value registrations must be prepared with. + pub audience: String, + pub server_time: u64, + pub version: String, +} + +/// A wake found in a push. +#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq)] +pub struct WakeEnvelope { + /// 0 for the legacy envelope, 1 for wake v1. + pub version: u8, + /// The wake id to acknowledge (v1 and fallback only). + pub id: Option, + /// The topic; `source.type` for legacy envelopes. + pub topic: String, + /// `background`, `alert` or `time_sensitive` (v1 only). + pub urgency: Option, + /// Unix seconds after which the wake is stale (v1 only). + pub deadline: Option, + /// The producer payload as raw JSON, when there is one. + pub payload_json: Option, + /// `createdAt` of a legacy envelope. + pub created_at: Option, + /// True for a fallback alert, which carries nothing to decrypt. + pub fallback: bool, +} + +/// A topic devices can subscribe to. +#[derive(uniffi::Record, Debug, Clone, PartialEq, Eq)] +pub struct WakeTopic { + pub name: String, + pub description: String, + /// `background`, `alert` or `time_sensitive`. + pub urgency: String, + pub deadline_secs: u32, + /// Whether a visible fallback alert may be shown for this topic. + pub alertable: bool, + /// Alert title, for topics with display text. + pub title: Option, + /// Alert body, for topics with display text. + pub body: Option, + /// Peers (not services) send this topic, through grants. + pub peer: bool, +}