From c530f58f1b3535e7fd2b5634fc37c7961b451c25 Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Mon, 27 Mar 2023 19:57:00 +0500 Subject: [PATCH 1/9] IOS-3219 Create wallet from the seed --- Example/TangemSdkExample/AppModel.swift | 15 +++++++ Example/TangemSdkExample/ContentView.swift | 4 ++ .../Developer/StaticData.swift | 1 + .../TangemSdk/Common/Card/CardSettings.swift | 4 ++ .../Common/Card/FirmwareVersion.swift | 2 + TangemSdk/TangemSdk/Common/Card/Wallet.swift | 6 +++ .../Common/Core/TangemSdkError.swift | 2 + .../Deserialization/CardDeserializer.swift | 1 + .../Deserialization/WalletDeserializer.swift | 6 ++- .../TangemSdk/Common/JSON/Handlers.swift | 3 +- TangemSdk/TangemSdk/Common/TLV/TlvTag.swift | 1 + TangemSdk/TangemSdk/Crypto/CryptoUtils.swift | 1 + .../Personalization/Entities/CardConfig.swift | 7 ++- .../Wallet/CreateWalletCommand.swift | 27 ++++++++++-- .../Operations/Wallet/CreateWalletTask.swift | 7 ++- TangemSdk/TangemSdk/TangemSdk.swift | 4 +- TangemSdk/TangemSdkTests/BIP32Tests.swift | 43 +++++++++++++++++++ TangemSdk/TangemSdkTests/JSONRPCTests.swift | 1 + TangemSdk/TangemSdkTests/Jsons/Card.json | 6 ++- .../TangemSdkTests/Jsons/CreateWallet.json | 1 + 20 files changed, 131 insertions(+), 11 deletions(-) diff --git a/Example/TangemSdkExample/AppModel.swift b/Example/TangemSdkExample/AppModel.swift index 305a7d0c7..9b8e21efa 100644 --- a/Example/TangemSdkExample/AppModel.swift +++ b/Example/TangemSdkExample/AppModel.swift @@ -17,6 +17,7 @@ class AppModel: ObservableObject { //Wallet creation @Published var curve: EllipticCurve = .secp256k1 + @Published var mnemonicString: String = "" //Sign @Published var derivationPath: String = "" //Attestation @@ -285,9 +286,23 @@ extension AppModel { self.complete(with: "Scan card to retrieve cardId") return } + + var seed: Data? = nil + + if !mnemonicString.isEmpty { + do { + let mnemonic = try Mnemonic(with: mnemonicString) + seed = try mnemonic.generateSeed() + } + catch { + self.complete(with: error) + return + } + } tangemSdk.createWallet(curve: curve, cardId: cardId, + seed: seed, completion: handleCompletion) } diff --git a/Example/TangemSdkExample/ContentView.swift b/Example/TangemSdkExample/ContentView.swift index 3d518be89..6504556f8 100644 --- a/Example/TangemSdkExample/ContentView.swift +++ b/Example/TangemSdkExample/ContentView.swift @@ -140,6 +140,10 @@ struct ContentView: View { } .pickerStyle(SegmentedPickerStyle()) } + + TextField("Optional mnemonic", text: $model.mnemonicString) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .autocapitalization(.none) } .padding() .cornerRadius(8) diff --git a/Example/TangemSdkExample/Developer/StaticData.swift b/Example/TangemSdkExample/Developer/StaticData.swift index 5f294622b..a3befad84 100644 --- a/Example/TangemSdkExample/Developer/StaticData.swift +++ b/Example/TangemSdkExample/Developer/StaticData.swift @@ -48,6 +48,7 @@ extension AppModel { "disableFiles": false, "allowHDWallets": true, "allowBackup": true, + "allowExternalWallets": true, "NDEF": [], "cardData": { "date": "2021-03-15", diff --git a/TangemSdk/TangemSdk/Common/Card/CardSettings.swift b/TangemSdk/TangemSdk/Common/Card/CardSettings.swift index e6063332b..4d6a6d51e 100644 --- a/TangemSdk/TangemSdk/Common/Card/CardSettings.swift +++ b/TangemSdk/TangemSdk/Common/Card/CardSettings.swift @@ -32,6 +32,8 @@ public extension Card { public let isHDWalletAllowed: Bool /// Is allowed to create backup public let isBackupAllowed: Bool + /// Is allowed to create or import external wallets. COS. v6.10+ + public let isExternalWalletsAllowed: Bool /// Is allowed to delete wallet. COS before v4 @SkipEncoding var isPermanentWallet: Bool @@ -71,6 +73,7 @@ extension Card.Settings { self.isHDWalletAllowed = mask.contains(.allowHDWallets) self.isFilesAllowed = !mask.contains(.disableFiles) self.isBackupAllowed = mask.contains(.allowBackup) + self.isExternalWalletsAllowed = mask.contains(.allowExternalWallets) var encryptionModes: [EncryptionMode] = [.strong] if mask.contains(.allowFastEncryption) { @@ -145,6 +148,7 @@ extension CardSettingsMask { static let isReusable = CardSettingsMask(rawValue: 0x0001) static let allowHDWallets = CardSettingsMask(rawValue: 0x00200000) static let allowBackup = CardSettingsMask(rawValue: 0x00400000) + static let allowExternalWallets = CardSettingsMask(rawValue: 0x00800000) } //MARK:- CardSettingsMask OptionSetCodable conformance diff --git a/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift b/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift index 98a0d304c..716333d89 100644 --- a/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift +++ b/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift @@ -125,6 +125,8 @@ public extension FirmwareVersion { //todo: move all doubleValue checks to consta static let backupAvailable = FirmwareVersion(major: 4, minor: 39) /// Wallet ownership confirmation available static let walletOwnershipConfirmationAvailable = FirmwareVersion(major: 2, minor: 1) + /// External wallets support + static let isExternalWalletsAvailable = FirmwareVersion(major: 6, minor: 10) /// Tmp range for visa cards static let visaRange = 5.25...5.30 } diff --git a/TangemSdk/TangemSdk/Common/Card/Wallet.swift b/TangemSdk/TangemSdk/Common/Card/Wallet.swift index eb44056c7..419bee985 100644 --- a/TangemSdk/TangemSdk/Common/Card/Wallet.swift +++ b/TangemSdk/TangemSdk/Common/Card/Wallet.swift @@ -30,6 +30,8 @@ public extension Card { public let index: Int /// Proof for BLS Proof of possession scheme (POP) public let proof: Data? + /// Does this wallet has an external copy. E.g. seed phrase + public let isExternal: Bool /// Does this wallet has a backup public var hasBackup: Bool /// Derived keys according to `Config.defaultDerivationPaths` @@ -59,6 +61,10 @@ extension Card.Wallet { case backuped = 0x82 /// Wallet was purged and can't be recreated or used for signing, but backup data read and wallet can be usable on backup card case backupedAndPurged = 0x83 + /// Wallet was loaded externally + case external = 0x42 + /// Wallet was loaded externally and backuped + case backupedExternal = 0xC2 } } diff --git a/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift b/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift index 1c80c7182..769a9b46c 100644 --- a/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift +++ b/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift @@ -278,6 +278,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable { //MARK: Settings case filesDisabled case hdWalletDisabled + case externalWalletsDisabled case resetPinNoCardToReset case resetPinWrongCard(internalCode: Int? = nil) @@ -399,6 +400,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable { case .filesDisabled: return 42002 case .hdWalletDisabled: return 42003 + case .externalWalletsDisabled: return 42004 // MARK: 5xxxx Errors // SDK error. Errors, that occurred in the upper level of SDK, like device restrictions, user canceled the operation or SDK is busy and can’t open the new session right now. diff --git a/TangemSdk/TangemSdk/Common/Deserialization/CardDeserializer.swift b/TangemSdk/TangemSdk/Common/Deserialization/CardDeserializer.swift index 580c1ce70..e237718b3 100644 --- a/TangemSdk/TangemSdk/Common/Deserialization/CardDeserializer.swift +++ b/TangemSdk/TangemSdk/Common/Deserialization/CardDeserializer.swift @@ -60,6 +60,7 @@ struct CardDeserializer { remainingSignatures: remainingSignatures, index: 0, proof: nil, + isExternal: false, hasBackup: false) wallets.append(wallet) diff --git a/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift b/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift index 5669cd610..fed69cd68 100644 --- a/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift +++ b/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift @@ -44,7 +44,8 @@ class WalletDeserializer { func deserializeWallet(from decoder: TlvDecoder) throws -> Card.Wallet { let status: Card.Wallet.Status = try decoder.decode(.status) - guard status == .loaded || status == .backuped else { //We need only loaded wallets + + if status == .empty || status == .purged || status == .backupedAndPurged { //We need only loaded wallets throw TangemSdkError.walletNotFound } @@ -64,6 +65,7 @@ class WalletDeserializer { remainingSignatures: nil, index: try decoder.decode(.walletIndex), proof: try decoder.decode(.proof), - hasBackup: status == .backuped) + isExternal: status == .external || status == .backupedExternal, + hasBackup: status == .backuped || status == .backupedExternal) } } diff --git a/TangemSdk/TangemSdk/Common/JSON/Handlers.swift b/TangemSdk/TangemSdk/Common/JSON/Handlers.swift index 43df7666f..52850d23c 100644 --- a/TangemSdk/TangemSdk/Common/JSON/Handlers.swift +++ b/TangemSdk/TangemSdk/Common/JSON/Handlers.swift @@ -61,7 +61,8 @@ class CreateWalletHandler: JSONRPCHandler { func makeRunnable(from parameters: [String : Any]) throws -> AnyJSONRPCRunnable { let curve: EllipticCurve = try parameters.value(for: "curve") - let command = CreateWalletTask(curve: curve) + let seed: Data? = try parameters.value(for: "seed") + let command = CreateWalletTask(curve: curve, seed: seed) return command.eraseToAnyRunnable() } } diff --git a/TangemSdk/TangemSdk/Common/TLV/TlvTag.swift b/TangemSdk/TangemSdk/Common/TLV/TlvTag.swift index 0c52567f4..e2bebe005 100644 --- a/TangemSdk/TangemSdk/Common/TLV/TlvTag.swift +++ b/TangemSdk/TangemSdk/Common/TLV/TlvTag.swift @@ -134,6 +134,7 @@ public enum TlvTag: Byte { // MARK: - HDWallet case walletHDPath = 0x6A case walletHDChain = 0x6B + case walletPrivateKey = 0x6F // MARK: - Backup case certificate = 0x55 diff --git a/TangemSdk/TangemSdk/Crypto/CryptoUtils.swift b/TangemSdk/TangemSdk/Crypto/CryptoUtils.swift index ee0bf752d..36658dbe4 100644 --- a/TangemSdk/TangemSdk/Crypto/CryptoUtils.swift +++ b/TangemSdk/TangemSdk/Crypto/CryptoUtils.swift @@ -79,6 +79,7 @@ public enum CryptoUtils { } } + // We can create only decompressed secp256r1 key here. public static func makePublicKey(from privateKey: Data, curve: EllipticCurve) throws -> Data { switch curve { case .secp256k1: diff --git a/TangemSdk/TangemSdk/Operations/Personalization/Entities/CardConfig.swift b/TangemSdk/TangemSdk/Operations/Personalization/Entities/CardConfig.swift index 12b7055c0..1cb36d625 100644 --- a/TangemSdk/TangemSdk/Operations/Personalization/Entities/CardConfig.swift +++ b/TangemSdk/TangemSdk/Operations/Personalization/Entities/CardConfig.swift @@ -53,6 +53,7 @@ public struct CardConfig: Decodable { let disableFiles: Bool? let allowHDWallets: Bool? //TODO: add precheck to specific commands let allowBackup: Bool? + let allowExternalWallets: Bool? let createWallet: Int let cardData: CardConfigData let ndefRecords: [NdefRecord] @@ -68,7 +69,7 @@ public struct CardConfig: Decodable { useActivation, useBlock, allowSelectBlockchain, skipSecurityDelayIfValidatedByIssuer, skipSecurityDelayIfValidatedByLinkedTerminal, disableIssuerData, disableUserData, disableFiles, createWallet, cardData, walletsCount, useDynamicNDEF, useOneCommandAtTime, protectIssuerDataAgainstReplay, - disablePrecomputedNDEF, allowHDWallets, allowBackup, isReusable + disablePrecomputedNDEF, allowHDWallets, allowBackup, isReusable, allowExternalWallets case pin = "PIN" case pin2 = "PIN2" case pin3 = "PIN3" @@ -171,6 +172,10 @@ public struct CardConfig: Decodable { if allowBackup ?? false { builder.add(.allowBackup) } + + if allowExternalWallets ?? false { + builder.add(.allowExternalWallets) + } return builder.build() } diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift index 580c76077..d15cfb7bd 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift @@ -32,12 +32,15 @@ final class CreateWalletCommand: Command { var walletIndex: Int = 0 private let curve: EllipticCurve + private let seed: Data? private let signingMethod = SigningMethod.signHash /// Default initializer /// - Parameter curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card - init(curve: EllipticCurve) { + /// - Parameter seed: An optional BIP39 seed to create wallet from. COS v6.10+. + init(curve: EllipticCurve, seed: Data?) { self.curve = curve + self.seed = seed } deinit { @@ -54,13 +57,23 @@ final class CreateWalletCommand: Command { return TangemSdkError.unsupportedCurve } - if card.firmwareVersion < FirmwareVersion.multiwalletAvailable { + if card.firmwareVersion < .multiwalletAvailable { if let cardSigningMethods = card.settings.defaultSigningMethods, !signingMethod.isSubset(of: cardSigningMethods) { return TangemSdkError.unsupportedWalletConfig } } - + + if seed != nil { + if card.firmwareVersion < .isExternalWalletsAvailable { + return TangemSdkError.notSupportedFirmwareVersion + } + + if !card.settings.isExternalWalletsAllowed { + return TangemSdkError.externalWalletsDisabled + } + } + return nil } @@ -116,6 +129,13 @@ final class CreateWalletCommand: Command { .append(.signingMethod, value: signingMethod) .append(.walletIndex, value: walletIndex) } + + if let seed { + let key = try BIP32().makeMasterKey(from: seed, curve: curve) + + try tlvBuilder.append(.walletPrivateKey, value: key.privateKey) + try tlvBuilder.append(.walletHDChain, value: key.chainCode) + } return CommandApdu(.createWallet, tlv: tlvBuilder.serialize()) } @@ -165,6 +185,7 @@ final class CreateWalletCommand: Command { remainingSignatures: remainingSignatures, index: index, proof: nil, + isExternal: false, hasBackup: false) } diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift index b41221a16..98a429859 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift @@ -20,11 +20,14 @@ import Foundation @available(iOS 13.0, *) public class CreateWalletTask: CardSessionRunnable { private let curve: EllipticCurve + private let seed: Data? private var derivationTask: DeriveWalletPublicKeysTask? = nil /// Default initializer /// - Parameter curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card - public init(curve: EllipticCurve) { + /// - Parameter seed: An optional BIP39 seed to create wallet from. COS v6.10+. Nil by default. + public init(curve: EllipticCurve, seed: Data? = nil) { self.curve = curve + self.seed = seed } deinit { @@ -32,7 +35,7 @@ public class CreateWalletTask: CardSessionRunnable { } public func run(in session: CardSession, completion: @escaping CompletionResult) { - let command = CreateWalletCommand(curve: curve) + let command = CreateWalletCommand(curve: curve, seed: seed) command.run(in: session) { result in switch result { case .success(let response): diff --git a/TangemSdk/TangemSdk/TangemSdk.swift b/TangemSdk/TangemSdk/TangemSdk.swift index 958588387..269ade36a 100644 --- a/TangemSdk/TangemSdk/TangemSdk.swift +++ b/TangemSdk/TangemSdk/TangemSdk.swift @@ -140,12 +140,14 @@ public extension TangemSdk { /// - curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card /// - initialMessage: A custom description that shows at the beginning of the NFC session. If nil, default message will be used /// - cardId: CID, Unique Tangem card ID number. + /// - seed: An optional BIP39 seed to create wallet from. COS v.6.10+. Nil by default. /// - completion: Returns `Swift.Result` func createWallet(curve: EllipticCurve, cardId: String, + seed: Data? = nil, initialMessage: Message? = nil, completion: @escaping CompletionResult) { - let command = CreateWalletTask(curve: curve) + let command = CreateWalletTask(curve: curve, seed: seed) startSession(with: command, cardId: cardId, initialMessage: initialMessage, completion: completion) } diff --git a/TangemSdk/TangemSdkTests/BIP32Tests.swift b/TangemSdk/TangemSdkTests/BIP32Tests.swift index d24ca61c0..274d7cf05 100644 --- a/TangemSdk/TangemSdkTests/BIP32Tests.swift +++ b/TangemSdk/TangemSdkTests/BIP32Tests.swift @@ -8,6 +8,7 @@ import Foundation import XCTest +import CryptoKit @testable import TangemSdk @available(iOS 13.0, *) @@ -132,4 +133,46 @@ class BIP32Tests: XCTestCase { let xpub = try mPub.serialize(for: .mainnet) XCTAssertEqual(xpub, "xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa") } + + // MARK: - Test that keys uploaded to a card are equal to locally computed + + func testExternalWalletSecp256k1() throws { + let mnemonicString = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + let mnemonic = try Mnemonic(with: mnemonicString) + let seed = try mnemonic.generateSeed() + let privKey = try BIP32().makeMasterKey(from: seed, curve: .secp256k1) + let pubKey = try privKey.makePublicKey(for: .secp256k1) + + let publicKeyFromCard = "03D902F35F560E0470C63313C7369168D9D7DF2D49BF295FD9FB7CB109CCEE0494" + let chainCodeFromCard = "7923408DADD3C7B56EED15567707AE5E5DCA089DE972E07F3B860450E2A3B70E" + XCTAssertEqual(pubKey.publicKey.hexString, publicKeyFromCard) + XCTAssertEqual(pubKey.chainCode.hexString, chainCodeFromCard) + } + + func testExternalWalletEd25519() throws { + let mnemonicString = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + let mnemonic = try Mnemonic(with: mnemonicString) + let seed = try mnemonic.generateSeed() + let privKey = try BIP32().makeMasterKey(from: seed, curve: .ed25519) + let pubKey = try privKey.makePublicKey(for: .ed25519) + + let publicKeyFromCard = "E96B1C6B8769FDB0B34FBECFDF85C33B053CECAD9517E1AB88CBA614335775C1" + let chainCodeFromCard = "DDFA71109701BBF7C126C8C7AB5880B0DEC3D167A8FE6AFA7A9597DF0BBEE72B" + XCTAssertEqual(pubKey.publicKey.hexString, publicKeyFromCard) + XCTAssertEqual(pubKey.chainCode.hexString, chainCodeFromCard) + } + + @available(iOS 16.0, *) + func testExternalWalletSecp256r1() throws { + let mnemonicString = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + let mnemonic = try Mnemonic(with: mnemonicString) + let seed = try mnemonic.generateSeed() + let privKey = try BIP32().makeMasterKey(from: seed, curve: .secp256r1) + let pubKey = (try P256.Signing.PrivateKey(rawRepresentation: privKey.privateKey)).publicKey.compressedRepresentation + + let publicKeyFromCard = "029983A77B155ED3B3B9E1DDD223BD5AA073834C8F61113B2F1B883AAA70971B5F" + let chainCodeFromCard = "C7A888C4C670406E7AAEB6E86555CE0C4E738A337F9A9BC239F6D7E475110A4E" + XCTAssertEqual(pubKey.hexString, publicKeyFromCard) + XCTAssertEqual(privKey.chainCode.hexString, chainCodeFromCard) + } } diff --git a/TangemSdk/TangemSdkTests/JSONRPCTests.swift b/TangemSdk/TangemSdkTests/JSONRPCTests.swift index b0b41c03b..cdb2abaa5 100644 --- a/TangemSdk/TangemSdkTests/JSONRPCTests.swift +++ b/TangemSdk/TangemSdkTests/JSONRPCTests.swift @@ -135,6 +135,7 @@ class JSONRPCTests: XCTestCase { remainingSignatures: 100, index: 1, proof: nil, + isExternal: false, hasBackup: false)) testMethod(name: "CreateWallet", result: result) } diff --git a/TangemSdk/TangemSdkTests/Jsons/Card.json b/TangemSdk/TangemSdkTests/Jsons/Card.json index 0cac20bd8..7d46f1623 100644 --- a/TangemSdk/TangemSdkTests/Jsons/Card.json +++ b/TangemSdk/TangemSdkTests/Jsons/Card.json @@ -24,7 +24,8 @@ "isIssuerDataProtectedAgainstReplay" : true, "isHDWalletAllowed" : true, "isFilesAllowed" : true, - "isBackupAllowed" : true + "isBackupAllowed" : true, + "isExternalWalletsAllowed" : true }, "issuer" : { "name" : "TANGEM AG", @@ -60,6 +61,7 @@ }, "index" : 0, "hasBackup" : false, + "isExternal": false, "derivedKeys" : [] }, { @@ -71,6 +73,7 @@ }, "index" : 1, "hasBackup" : false, + "isExternal": false, "derivedKeys" : [] }, { @@ -82,6 +85,7 @@ }, "index" : 2, "hasBackup" : false, + "isExternal": false, "derivedKeys" : [] } ], diff --git a/TangemSdk/TangemSdkTests/Jsons/CreateWallet.json b/TangemSdk/TangemSdkTests/Jsons/CreateWallet.json index 92a79889f..9fab18387 100644 --- a/TangemSdk/TangemSdkTests/Jsons/CreateWallet.json +++ b/TangemSdk/TangemSdkTests/Jsons/CreateWallet.json @@ -21,6 +21,7 @@ "remainingSignatures": 100, "index": 1, "hasBackup" : false, + "isExternal": false, "derivedKeys" : [] } }, From 9e36f879be12b2d35e24f79c22e36388ed3c0df3 Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Mon, 27 Mar 2023 22:13:01 +0500 Subject: [PATCH 2/9] IOS-3219 Add precheck for duplicated wallet --- .../Common/Core/TangemSdkError.swift | 2 ++ .../Wallet/CreateWalletCommand.swift | 30 +++++++++++++------ .../Operations/Wallet/CreateWalletTask.swift | 26 +++++++++------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift b/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift index 769a9b46c..d6812f111 100644 --- a/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift +++ b/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift @@ -248,6 +248,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable { case walletNotFound case cardWithMaxZeroWallets case walletCannotBeCreated + case walletAlreadyCreated // MARK: Backup errors case backupFailedCardNotLinked @@ -344,6 +345,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable { case .accessCodeRequired: return 40401 case .walletCannotBeCreated: return 40403 case .cardWithMaxZeroWallets: return 40404 + case .walletAlreadyCreated: return 40405 case .alreadyCreated: return 40501 case .unsupportedCurve: return 40502 diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift index d15cfb7bd..c34612aa4 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift @@ -32,15 +32,19 @@ final class CreateWalletCommand: Command { var walletIndex: Int = 0 private let curve: EllipticCurve - private let seed: Data? + private let externalKey: ExtendedPrivateKey? private let signingMethod = SigningMethod.signHash /// Default initializer /// - Parameter curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card /// - Parameter seed: An optional BIP39 seed to create wallet from. COS v6.10+. - init(curve: EllipticCurve, seed: Data?) { + init(curve: EllipticCurve, seed: Data?) throws { self.curve = curve - self.seed = seed + if let seed { + self.externalKey = try BIP32().makeMasterKey(from: seed, curve: curve) + } else { + self.externalKey = nil + } } deinit { @@ -64,7 +68,7 @@ final class CreateWalletCommand: Command { } } - if seed != nil { + if externalKey != nil { if card.firmwareVersion < .isExternalWalletsAvailable { return TangemSdkError.notSupportedFirmwareVersion } @@ -72,6 +76,16 @@ final class CreateWalletCommand: Command { if !card.settings.isExternalWalletsAllowed { return TangemSdkError.externalWalletsDisabled } + + do { + // This check will fail for compressed secp256r1 keys + if let extendedKey = try externalKey?.makePublicKey(for: curve), + card.wallets[extendedKey.publicKey] != nil { + return TangemSdkError.walletAlreadyCreated + } + } catch { + return error.toTangemSdkError() + } } return nil @@ -130,11 +144,9 @@ final class CreateWalletCommand: Command { .append(.walletIndex, value: walletIndex) } - if let seed { - let key = try BIP32().makeMasterKey(from: seed, curve: curve) - - try tlvBuilder.append(.walletPrivateKey, value: key.privateKey) - try tlvBuilder.append(.walletHDChain, value: key.chainCode) + if let externalKey { + try tlvBuilder.append(.walletPrivateKey, value: externalKey.privateKey) + try tlvBuilder.append(.walletHDChain, value: externalKey.chainCode) } return CommandApdu(.createWallet, tlv: tlvBuilder.serialize()) diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift index 98a429859..21ac10cce 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift @@ -35,19 +35,23 @@ public class CreateWalletTask: CardSessionRunnable { } public func run(in session: CardSession, completion: @escaping CompletionResult) { - let command = CreateWalletCommand(curve: curve, seed: seed) - command.run(in: session) { result in - switch result { - case .success(let response): - self.deriveKeysIfNeeded(for: response, in: session, completion: completion) - case .failure(let error): - if case .invalidState = error { //Wallet already created but we didn't get the proper response from the card. Rescan and retrieve the wallet - Log.debug("Received wallet creation error. Try rescan and retrieve created wallet") - self.scanAndRetrieveCreatedWallet(at: command.walletIndex, in: session, completion: completion) - } else { - completion(.failure(error)) + do { + let command = try CreateWalletCommand(curve: curve, seed: seed) + command.run(in: session) { result in + switch result { + case .success(let response): + self.deriveKeysIfNeeded(for: response, in: session, completion: completion) + case .failure(let error): + if case .invalidState = error { //Wallet already created but we didn't get the proper response from the card. Rescan and retrieve the wallet + Log.debug("Received wallet creation error. Try rescan and retrieve created wallet") + self.scanAndRetrieveCreatedWallet(at: command.walletIndex, in: session, completion: completion) + } else { + completion(.failure(error)) + } } } + } catch { + completion(.failure(error.toTangemSdkError())) } } From 06f04d780307642675c7f399e8e4fddf85b42723 Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Mon, 27 Mar 2023 22:27:13 +0500 Subject: [PATCH 3/9] IOS-3219 Add status extension --- TangemSdk/TangemSdk/Common/Card/Wallet.swift | 30 +++++++++++++++++++ .../Deserialization/WalletDeserializer.swift | 6 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/TangemSdk/TangemSdk/Common/Card/Wallet.swift b/TangemSdk/TangemSdk/Common/Card/Wallet.swift index 419bee985..45b939262 100644 --- a/TangemSdk/TangemSdk/Common/Card/Wallet.swift +++ b/TangemSdk/TangemSdk/Common/Card/Wallet.swift @@ -68,6 +68,36 @@ extension Card.Wallet { } } +@available(iOS 13.0, *) +extension Card.Wallet.Status { + var isBackuped: Bool { + switch self { + case .backuped, .backupedAndPurged: + return true + default: + return false + } + } + + var isExternal: Bool { + switch self { + case .external, .backupedExternal: + return true + default: + return false + } + } + + var isLoaded: Bool { + switch self { + case .empty, .purged, .backupedAndPurged: + return false + default: + return true + } + } +} + @available(iOS 13.0, *) extension Card.Wallet.Settings { /// Stores and maps Wallet settings diff --git a/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift b/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift index fed69cd68..4f4d3d041 100644 --- a/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift +++ b/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift @@ -45,7 +45,7 @@ class WalletDeserializer { func deserializeWallet(from decoder: TlvDecoder) throws -> Card.Wallet { let status: Card.Wallet.Status = try decoder.decode(.status) - if status == .empty || status == .purged || status == .backupedAndPurged { //We need only loaded wallets + if !status.isLoaded { //We need only loaded wallets throw TangemSdkError.walletNotFound } @@ -65,7 +65,7 @@ class WalletDeserializer { remainingSignatures: nil, index: try decoder.decode(.walletIndex), proof: try decoder.decode(.proof), - isExternal: status == .external || status == .backupedExternal, - hasBackup: status == .backuped || status == .backupedExternal) + isExternal: status.isExternal, + hasBackup: status.isBackuped) } } From 1753447e1d34d91c0af0879f41a5e2b143c857ff Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Wed, 29 Mar 2023 14:28:33 +0500 Subject: [PATCH 4/9] IOS-3219 Fix backuped status --- TangemSdk/TangemSdk/Common/Card/Wallet.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TangemSdk/TangemSdk/Common/Card/Wallet.swift b/TangemSdk/TangemSdk/Common/Card/Wallet.swift index 45b939262..61331e292 100644 --- a/TangemSdk/TangemSdk/Common/Card/Wallet.swift +++ b/TangemSdk/TangemSdk/Common/Card/Wallet.swift @@ -72,7 +72,7 @@ extension Card.Wallet { extension Card.Wallet.Status { var isBackuped: Bool { switch self { - case .backuped, .backupedAndPurged: + case .backuped, .backupedAndPurged, .backupedExternal: return true default: return false From 162e9fd81930fefa1bb95a9236228d592eb98fb4 Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Wed, 29 Mar 2023 15:18:26 +0500 Subject: [PATCH 5/9] IOS-3219 Add new SW --- TangemSdk/TangemSdk/Common/APDU/StatusWord.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TangemSdk/TangemSdk/Common/APDU/StatusWord.swift b/TangemSdk/TangemSdk/Common/APDU/StatusWord.swift index 438814917..cb6a36468 100644 --- a/TangemSdk/TangemSdk/Common/APDU/StatusWord.swift +++ b/TangemSdk/TangemSdk/Common/APDU/StatusWord.swift @@ -30,6 +30,7 @@ public enum StatusWord: UInt16 { case walletNotFound = 0x6A88 case invalidAccessCode = 0x6AF1 case invalidPascode = 0x6AF2 + case walletAlreadyExists = 0x6A89 //case pinsNotChanged = 0x9000 //equal to processCompleted func toTangemSdkError() -> TangemSdkError? { @@ -54,6 +55,8 @@ public enum StatusWord: UInt16 { return TangemSdkError.accessCodeRequired case .invalidPascode: return TangemSdkError.passcodeRequired + case .walletAlreadyExists: + return TangemSdkError.walletAlreadyCreated default: return nil } From 922c2e0dc3128e0a3cc2c722e43f074d4bcf6b8c Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Wed, 29 Mar 2023 16:20:48 +0500 Subject: [PATCH 6/9] IOS-3219 Rename external wallet to keys import --- .../Developer/StaticData.swift | 2 +- .../TangemSdk/Common/Card/CardSettings.swift | 8 +++---- .../Common/Card/FirmwareVersion.swift | 4 ++-- TangemSdk/TangemSdk/Common/Card/Wallet.swift | 18 +++++++------- .../Common/Core/TangemSdkError.swift | 4 ++-- .../Deserialization/CardDeserializer.swift | 2 +- .../Deserialization/WalletDeserializer.swift | 2 +- .../Personalization/Entities/CardConfig.swift | 8 +++---- .../Wallet/CreateWalletCommand.swift | 24 +++++++++---------- TangemSdk/TangemSdkTests/BIP32Tests.swift | 6 ++--- TangemSdk/TangemSdkTests/JSONRPCTests.swift | 2 +- TangemSdk/TangemSdkTests/Jsons/Card.json | 8 +++---- .../TangemSdkTests/Jsons/CreateWallet.json | 2 +- 13 files changed, 45 insertions(+), 45 deletions(-) diff --git a/Example/TangemSdkExample/Developer/StaticData.swift b/Example/TangemSdkExample/Developer/StaticData.swift index a3befad84..277129fca 100644 --- a/Example/TangemSdkExample/Developer/StaticData.swift +++ b/Example/TangemSdkExample/Developer/StaticData.swift @@ -48,7 +48,7 @@ extension AppModel { "disableFiles": false, "allowHDWallets": true, "allowBackup": true, - "allowExternalWallets": true, + "allowKeysImport": true, "NDEF": [], "cardData": { "date": "2021-03-15", diff --git a/TangemSdk/TangemSdk/Common/Card/CardSettings.swift b/TangemSdk/TangemSdk/Common/Card/CardSettings.swift index 4d6a6d51e..a05f2bb1a 100644 --- a/TangemSdk/TangemSdk/Common/Card/CardSettings.swift +++ b/TangemSdk/TangemSdk/Common/Card/CardSettings.swift @@ -32,8 +32,8 @@ public extension Card { public let isHDWalletAllowed: Bool /// Is allowed to create backup public let isBackupAllowed: Bool - /// Is allowed to create or import external wallets. COS. v6.10+ - public let isExternalWalletsAllowed: Bool + /// Is allowed to import keys. COS. v6.10+ + public let isKeysImportAllowed: Bool /// Is allowed to delete wallet. COS before v4 @SkipEncoding var isPermanentWallet: Bool @@ -73,7 +73,7 @@ extension Card.Settings { self.isHDWalletAllowed = mask.contains(.allowHDWallets) self.isFilesAllowed = !mask.contains(.disableFiles) self.isBackupAllowed = mask.contains(.allowBackup) - self.isExternalWalletsAllowed = mask.contains(.allowExternalWallets) + self.isKeysImportAllowed = mask.contains(.allowKeysImport) var encryptionModes: [EncryptionMode] = [.strong] if mask.contains(.allowFastEncryption) { @@ -148,7 +148,7 @@ extension CardSettingsMask { static let isReusable = CardSettingsMask(rawValue: 0x0001) static let allowHDWallets = CardSettingsMask(rawValue: 0x00200000) static let allowBackup = CardSettingsMask(rawValue: 0x00400000) - static let allowExternalWallets = CardSettingsMask(rawValue: 0x00800000) + static let allowKeysImport = CardSettingsMask(rawValue: 0x00800000) } //MARK:- CardSettingsMask OptionSetCodable conformance diff --git a/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift b/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift index 716333d89..ca488b141 100644 --- a/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift +++ b/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift @@ -125,8 +125,8 @@ public extension FirmwareVersion { //todo: move all doubleValue checks to consta static let backupAvailable = FirmwareVersion(major: 4, minor: 39) /// Wallet ownership confirmation available static let walletOwnershipConfirmationAvailable = FirmwareVersion(major: 2, minor: 1) - /// External wallets support - static let isExternalWalletsAvailable = FirmwareVersion(major: 6, minor: 10) + /// Keys import support + static let keysImportAvailable = FirmwareVersion(major: 6, minor: 10) /// Tmp range for visa cards static let visaRange = 5.25...5.30 } diff --git a/TangemSdk/TangemSdk/Common/Card/Wallet.swift b/TangemSdk/TangemSdk/Common/Card/Wallet.swift index 61331e292..ee4399482 100644 --- a/TangemSdk/TangemSdk/Common/Card/Wallet.swift +++ b/TangemSdk/TangemSdk/Common/Card/Wallet.swift @@ -30,8 +30,8 @@ public extension Card { public let index: Int /// Proof for BLS Proof of possession scheme (POP) public let proof: Data? - /// Does this wallet has an external copy. E.g. seed phrase - public let isExternal: Bool + /// Has this key been imported to a card. E.g. from seed phrase + public let isImported: Bool /// Does this wallet has a backup public var hasBackup: Bool /// Derived keys according to `Config.defaultDerivationPaths` @@ -61,10 +61,10 @@ extension Card.Wallet { case backuped = 0x82 /// Wallet was purged and can't be recreated or used for signing, but backup data read and wallet can be usable on backup card case backupedAndPurged = 0x83 - /// Wallet was loaded externally - case external = 0x42 - /// Wallet was loaded externally and backuped - case backupedExternal = 0xC2 + /// Wallet was imported + case imported = 0x42 + /// Wallet was imported and backuped + case backupedImported = 0xC2 } } @@ -72,16 +72,16 @@ extension Card.Wallet { extension Card.Wallet.Status { var isBackuped: Bool { switch self { - case .backuped, .backupedAndPurged, .backupedExternal: + case .backuped, .backupedAndPurged, .backupedImported: return true default: return false } } - var isExternal: Bool { + var isImported: Bool { switch self { - case .external, .backupedExternal: + case .imported, .backupedImported: return true default: return false diff --git a/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift b/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift index d6812f111..255e71187 100644 --- a/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift +++ b/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift @@ -279,7 +279,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable { //MARK: Settings case filesDisabled case hdWalletDisabled - case externalWalletsDisabled + case keysImportDisabled case resetPinNoCardToReset case resetPinWrongCard(internalCode: Int? = nil) @@ -402,7 +402,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable { case .filesDisabled: return 42002 case .hdWalletDisabled: return 42003 - case .externalWalletsDisabled: return 42004 + case .keysImportDisabled: return 42004 // MARK: 5xxxx Errors // SDK error. Errors, that occurred in the upper level of SDK, like device restrictions, user canceled the operation or SDK is busy and can’t open the new session right now. diff --git a/TangemSdk/TangemSdk/Common/Deserialization/CardDeserializer.swift b/TangemSdk/TangemSdk/Common/Deserialization/CardDeserializer.swift index e237718b3..f585e4ba9 100644 --- a/TangemSdk/TangemSdk/Common/Deserialization/CardDeserializer.swift +++ b/TangemSdk/TangemSdk/Common/Deserialization/CardDeserializer.swift @@ -60,7 +60,7 @@ struct CardDeserializer { remainingSignatures: remainingSignatures, index: 0, proof: nil, - isExternal: false, + isImported: false, hasBackup: false) wallets.append(wallet) diff --git a/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift b/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift index 4f4d3d041..aeb67387b 100644 --- a/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift +++ b/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift @@ -65,7 +65,7 @@ class WalletDeserializer { remainingSignatures: nil, index: try decoder.decode(.walletIndex), proof: try decoder.decode(.proof), - isExternal: status.isExternal, + isImported: status.isImported, hasBackup: status.isBackuped) } } diff --git a/TangemSdk/TangemSdk/Operations/Personalization/Entities/CardConfig.swift b/TangemSdk/TangemSdk/Operations/Personalization/Entities/CardConfig.swift index 1cb36d625..9638e944c 100644 --- a/TangemSdk/TangemSdk/Operations/Personalization/Entities/CardConfig.swift +++ b/TangemSdk/TangemSdk/Operations/Personalization/Entities/CardConfig.swift @@ -53,7 +53,7 @@ public struct CardConfig: Decodable { let disableFiles: Bool? let allowHDWallets: Bool? //TODO: add precheck to specific commands let allowBackup: Bool? - let allowExternalWallets: Bool? + let allowKeysImport: Bool? let createWallet: Int let cardData: CardConfigData let ndefRecords: [NdefRecord] @@ -69,7 +69,7 @@ public struct CardConfig: Decodable { useActivation, useBlock, allowSelectBlockchain, skipSecurityDelayIfValidatedByIssuer, skipSecurityDelayIfValidatedByLinkedTerminal, disableIssuerData, disableUserData, disableFiles, createWallet, cardData, walletsCount, useDynamicNDEF, useOneCommandAtTime, protectIssuerDataAgainstReplay, - disablePrecomputedNDEF, allowHDWallets, allowBackup, isReusable, allowExternalWallets + disablePrecomputedNDEF, allowHDWallets, allowBackup, isReusable, allowKeysImport case pin = "PIN" case pin2 = "PIN2" case pin3 = "PIN3" @@ -173,8 +173,8 @@ public struct CardConfig: Decodable { builder.add(.allowBackup) } - if allowExternalWallets ?? false { - builder.add(.allowExternalWallets) + if allowKeysImport ?? false { + builder.add(.allowKeysImport) } return builder.build() diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift index c34612aa4..f84981732 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift @@ -32,7 +32,7 @@ final class CreateWalletCommand: Command { var walletIndex: Int = 0 private let curve: EllipticCurve - private let externalKey: ExtendedPrivateKey? + private let keyToImport: ExtendedPrivateKey? private let signingMethod = SigningMethod.signHash /// Default initializer @@ -41,9 +41,9 @@ final class CreateWalletCommand: Command { init(curve: EllipticCurve, seed: Data?) throws { self.curve = curve if let seed { - self.externalKey = try BIP32().makeMasterKey(from: seed, curve: curve) + self.keyToImport = try BIP32().makeMasterKey(from: seed, curve: curve) } else { - self.externalKey = nil + self.keyToImport = nil } } @@ -68,18 +68,18 @@ final class CreateWalletCommand: Command { } } - if externalKey != nil { - if card.firmwareVersion < .isExternalWalletsAvailable { + if keyToImport != nil { + if card.firmwareVersion < .keysImportAvailable { return TangemSdkError.notSupportedFirmwareVersion } - if !card.settings.isExternalWalletsAllowed { - return TangemSdkError.externalWalletsDisabled + if !card.settings.isKeysImportAllowed { + return TangemSdkError.keysImportDisabled } do { // This check will fail for compressed secp256r1 keys - if let extendedKey = try externalKey?.makePublicKey(for: curve), + if let extendedKey = try keyToImport?.makePublicKey(for: curve), card.wallets[extendedKey.publicKey] != nil { return TangemSdkError.walletAlreadyCreated } @@ -144,9 +144,9 @@ final class CreateWalletCommand: Command { .append(.walletIndex, value: walletIndex) } - if let externalKey { - try tlvBuilder.append(.walletPrivateKey, value: externalKey.privateKey) - try tlvBuilder.append(.walletHDChain, value: externalKey.chainCode) + if let keyToImport { + try tlvBuilder.append(.walletPrivateKey, value: keyToImport.privateKey) + try tlvBuilder.append(.walletHDChain, value: keyToImport.chainCode) } return CommandApdu(.createWallet, tlv: tlvBuilder.serialize()) @@ -197,7 +197,7 @@ final class CreateWalletCommand: Command { remainingSignatures: remainingSignatures, index: index, proof: nil, - isExternal: false, + isImported: false, hasBackup: false) } diff --git a/TangemSdk/TangemSdkTests/BIP32Tests.swift b/TangemSdk/TangemSdkTests/BIP32Tests.swift index 274d7cf05..01e98d2af 100644 --- a/TangemSdk/TangemSdkTests/BIP32Tests.swift +++ b/TangemSdk/TangemSdkTests/BIP32Tests.swift @@ -136,7 +136,7 @@ class BIP32Tests: XCTestCase { // MARK: - Test that keys uploaded to a card are equal to locally computed - func testExternalWalletSecp256k1() throws { + func testKeyImportSecp256k1() throws { let mnemonicString = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" let mnemonic = try Mnemonic(with: mnemonicString) let seed = try mnemonic.generateSeed() @@ -149,7 +149,7 @@ class BIP32Tests: XCTestCase { XCTAssertEqual(pubKey.chainCode.hexString, chainCodeFromCard) } - func testExternalWalletEd25519() throws { + func testKeyImportEd25519() throws { let mnemonicString = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" let mnemonic = try Mnemonic(with: mnemonicString) let seed = try mnemonic.generateSeed() @@ -163,7 +163,7 @@ class BIP32Tests: XCTestCase { } @available(iOS 16.0, *) - func testExternalWalletSecp256r1() throws { + func testKeyImportSecp256r1() throws { let mnemonicString = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" let mnemonic = try Mnemonic(with: mnemonicString) let seed = try mnemonic.generateSeed() diff --git a/TangemSdk/TangemSdkTests/JSONRPCTests.swift b/TangemSdk/TangemSdkTests/JSONRPCTests.swift index cdb2abaa5..6977ffec6 100644 --- a/TangemSdk/TangemSdkTests/JSONRPCTests.swift +++ b/TangemSdk/TangemSdkTests/JSONRPCTests.swift @@ -135,7 +135,7 @@ class JSONRPCTests: XCTestCase { remainingSignatures: 100, index: 1, proof: nil, - isExternal: false, + isImported: false, hasBackup: false)) testMethod(name: "CreateWallet", result: result) } diff --git a/TangemSdk/TangemSdkTests/Jsons/Card.json b/TangemSdk/TangemSdkTests/Jsons/Card.json index 7d46f1623..c096eaaee 100644 --- a/TangemSdk/TangemSdkTests/Jsons/Card.json +++ b/TangemSdk/TangemSdkTests/Jsons/Card.json @@ -25,7 +25,7 @@ "isHDWalletAllowed" : true, "isFilesAllowed" : true, "isBackupAllowed" : true, - "isExternalWalletsAllowed" : true + "isKeysImportAllowed" : true }, "issuer" : { "name" : "TANGEM AG", @@ -61,7 +61,7 @@ }, "index" : 0, "hasBackup" : false, - "isExternal": false, + "isImported": false, "derivedKeys" : [] }, { @@ -73,7 +73,7 @@ }, "index" : 1, "hasBackup" : false, - "isExternal": false, + "isImported": false, "derivedKeys" : [] }, { @@ -85,7 +85,7 @@ }, "index" : 2, "hasBackup" : false, - "isExternal": false, + "isImported": false, "derivedKeys" : [] } ], diff --git a/TangemSdk/TangemSdkTests/Jsons/CreateWallet.json b/TangemSdk/TangemSdkTests/Jsons/CreateWallet.json index 9fab18387..4fb7c37fd 100644 --- a/TangemSdk/TangemSdkTests/Jsons/CreateWallet.json +++ b/TangemSdk/TangemSdkTests/Jsons/CreateWallet.json @@ -21,7 +21,7 @@ "remainingSignatures": 100, "index": 1, "hasBackup" : false, - "isExternal": false, + "isImported": false, "derivedKeys" : [] } }, From f1125acf29d08616257d41987fe228089c531ac8 Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Wed, 29 Mar 2023 16:27:59 +0500 Subject: [PATCH 7/9] IOS-3219 Bump fw version --- TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift b/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift index ca488b141..9df89ebe5 100644 --- a/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift +++ b/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift @@ -126,7 +126,7 @@ public extension FirmwareVersion { //todo: move all doubleValue checks to consta /// Wallet ownership confirmation available static let walletOwnershipConfirmationAvailable = FirmwareVersion(major: 2, minor: 1) /// Keys import support - static let keysImportAvailable = FirmwareVersion(major: 6, minor: 10) + static let keysImportAvailable = FirmwareVersion(major: 6, minor: 11) /// Tmp range for visa cards static let visaRange = 5.25...5.30 } From 00213fcfbfc86ad24e4946da4c5155c97089c959 Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Thu, 30 Mar 2023 14:26:10 +0500 Subject: [PATCH 8/9] IOS-3219 Fix typos and renamings --- TangemSdk/TangemSdk/Common/Card/Wallet.swift | 18 +++++++-------- .../Common/Core/TangemSdkError.swift | 4 ++-- .../Deserialization/WalletDeserializer.swift | 4 ++-- TangemSdk/TangemSdk/Crypto/BIP39/BIP39.swift | 8 +++---- .../Backup/ResetBackupCommand.swift | 2 +- .../Wallet/CreateWalletCommand.swift | 22 +++++++++---------- TangemSdk/TangemSdk/TangemSdk.swift | 4 ++-- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/TangemSdk/TangemSdk/Common/Card/Wallet.swift b/TangemSdk/TangemSdk/Common/Card/Wallet.swift index ee4399482..868142675 100644 --- a/TangemSdk/TangemSdk/Common/Card/Wallet.swift +++ b/TangemSdk/TangemSdk/Common/Card/Wallet.swift @@ -58,21 +58,21 @@ extension Card.Wallet { /// Wallet was purged and can't be recreated or used for signing case purged = 3 /// Wallet created and can be used for signing, backup data read - case backuped = 0x82 + case backedUp = 0x82 /// Wallet was purged and can't be recreated or used for signing, but backup data read and wallet can be usable on backup card - case backupedAndPurged = 0x83 + case backedUpAndPurged = 0x83 /// Wallet was imported case imported = 0x42 - /// Wallet was imported and backuped - case backupedImported = 0xC2 + /// Wallet was imported and backed up + case backedUpImported = 0xC2 } } @available(iOS 13.0, *) extension Card.Wallet.Status { - var isBackuped: Bool { + var isBackedUp: Bool { switch self { - case .backuped, .backupedAndPurged, .backupedImported: + case .backedUp, .backedUpAndPurged, .backedUpImported: return true default: return false @@ -81,16 +81,16 @@ extension Card.Wallet.Status { var isImported: Bool { switch self { - case .imported, .backupedImported: + case .imported, .backedUpImported: return true default: return false } } - var isLoaded: Bool { + var isAvailable: Bool { switch self { - case .empty, .purged, .backupedAndPurged: + case .empty, .purged, .backedUpAndPurged: return false default: return true diff --git a/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift b/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift index 255e71187..139bd96dd 100644 --- a/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift +++ b/TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift @@ -265,7 +265,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable { case issuerSignatureLoadingFailed case accessCodeOrPasscodeRequired case noActiveBackup - case resetBackupFailedHasBackupedWallets + case resetBackupFailedHasBackedUpWallets case backupServiceInvalidState case noBackupCardForIndex case emptyBackupCards @@ -382,7 +382,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable { case .certificateSignatureRequired: return 41211 case .accessCodeOrPasscodeRequired: return 41212 case .noActiveBackup: return 41220 - case .resetBackupFailedHasBackupedWallets: return 41221 + case .resetBackupFailedHasBackedUpWallets: return 41221 case .backupServiceInvalidState: return 41222 case .noBackupCardForIndex: return 41223 case .emptyBackupCards: return 41224 diff --git a/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift b/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift index aeb67387b..b030729d7 100644 --- a/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift +++ b/TangemSdk/TangemSdk/Common/Deserialization/WalletDeserializer.swift @@ -45,7 +45,7 @@ class WalletDeserializer { func deserializeWallet(from decoder: TlvDecoder) throws -> Card.Wallet { let status: Card.Wallet.Status = try decoder.decode(.status) - if !status.isLoaded { //We need only loaded wallets + if !status.isAvailable { throw TangemSdkError.walletNotFound } @@ -66,6 +66,6 @@ class WalletDeserializer { index: try decoder.decode(.walletIndex), proof: try decoder.decode(.proof), isImported: status.isImported, - hasBackup: status.isBackuped) + hasBackup: status.isBackedUp) } } diff --git a/TangemSdk/TangemSdk/Crypto/BIP39/BIP39.swift b/TangemSdk/TangemSdk/Crypto/BIP39/BIP39.swift index eb966f205..84aba8c0b 100644 --- a/TangemSdk/TangemSdk/Crypto/BIP39/BIP39.swift +++ b/TangemSdk/TangemSdk/Crypto/BIP39/BIP39.swift @@ -120,17 +120,17 @@ public struct BIP39 { let entropyBits = entropyData.toBits() let concatenatedBits = entropyBits + entropyChecksumBits - let bitIndexes = concatenatedBits.chunked(into: 11) - let indexes = bitIndexes.compactMap { Int($0.joined(), radix: 2) } + let bitIndices = concatenatedBits.chunked(into: 11) + let indices = bitIndices.compactMap { Int($0.joined(), radix: 2) } - guard indexes.count == entropyLength.wordCount else { + guard indices.count == entropyLength.wordCount else { throw MnemonicError.mnenmonicCreationFailed } let allWords = wordlist.words let maxWordIndex = allWords.count - let words = try indexes.map { index in + let words = try indices.map { index in guard index < maxWordIndex else { throw MnemonicError.mnenmonicCreationFailed } diff --git a/TangemSdk/TangemSdk/Operations/Backup/ResetBackupCommand.swift b/TangemSdk/TangemSdk/Operations/Backup/ResetBackupCommand.swift index 6e7b372d6..d0a421fc5 100644 --- a/TangemSdk/TangemSdk/Operations/Backup/ResetBackupCommand.swift +++ b/TangemSdk/TangemSdk/Operations/Backup/ResetBackupCommand.swift @@ -39,7 +39,7 @@ public final class ResetBackupCommand: Command { } guard !card.wallets.contains(where: { $0.hasBackup } ) else { - return TangemSdkError.resetBackupFailedHasBackupedWallets + return TangemSdkError.resetBackupFailedHasBackedUpWallets } return nil diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift index f84981732..ea0cb26d4 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift @@ -32,7 +32,7 @@ final class CreateWalletCommand: Command { var walletIndex: Int = 0 private let curve: EllipticCurve - private let keyToImport: ExtendedPrivateKey? + private let privateKey: ExtendedPrivateKey? private let signingMethod = SigningMethod.signHash /// Default initializer @@ -41,9 +41,9 @@ final class CreateWalletCommand: Command { init(curve: EllipticCurve, seed: Data?) throws { self.curve = curve if let seed { - self.keyToImport = try BIP32().makeMasterKey(from: seed, curve: curve) + self.privateKey = try BIP32().makeMasterKey(from: seed, curve: curve) } else { - self.keyToImport = nil + self.privateKey = nil } } @@ -68,7 +68,7 @@ final class CreateWalletCommand: Command { } } - if keyToImport != nil { + if privateKey != nil { if card.firmwareVersion < .keysImportAvailable { return TangemSdkError.notSupportedFirmwareVersion } @@ -79,7 +79,7 @@ final class CreateWalletCommand: Command { do { // This check will fail for compressed secp256r1 keys - if let extendedKey = try keyToImport?.makePublicKey(for: curve), + if let extendedKey = try privateKey?.makePublicKey(for: curve), card.wallets[extendedKey.publicKey] != nil { return TangemSdkError.walletAlreadyCreated } @@ -144,9 +144,9 @@ final class CreateWalletCommand: Command { .append(.walletIndex, value: walletIndex) } - if let keyToImport { - try tlvBuilder.append(.walletPrivateKey, value: keyToImport.privateKey) - try tlvBuilder.append(.walletHDChain, value: keyToImport.chainCode) + if let privateKey { + try tlvBuilder.append(.walletPrivateKey, value: privateKey.privateKey) + try tlvBuilder.append(.walletHDChain, value: privateKey.chainCode) } return CommandApdu(.createWallet, tlv: tlvBuilder.serialize()) @@ -203,9 +203,9 @@ final class CreateWalletCommand: Command { private func calculateWalletIndex(for card: Card) throws -> Int { let maxIndex = card.settings.maxWalletsCount //We need to execute this wallet index calculation stuff only after precheck because of correct error mapping. Run fires only before precheck. And precheck will not fire if error handling disabled - let occupiedIndexes = card.wallets.map { $0.index } - let allIndexes = 0..` From acde40dfae6123d307a7b2bc4ad85ded2c82ec54 Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Thu, 30 Mar 2023 20:21:41 +0500 Subject: [PATCH 9/9] IOS-3219 Refactor create wallet --- Example/TangemSdkExample/AppModel.swift | 36 +++++++++++-------- Example/TangemSdkExample/ContentView.swift | 10 +++--- TangemSdk/TangemSdk.xcodeproj/project.pbxproj | 4 +++ .../TangemSdk/Common/JSON/Handlers.swift | 15 ++++++-- TangemSdk/TangemSdk/Common/JSON/JSONRPC.swift | 1 + .../Wallet/CreateWalletCommand.swift | 17 +++++---- .../Operations/Wallet/CreateWalletTask.swift | 22 ++++++++++-- TangemSdk/TangemSdk/TangemSdk.swift | 18 ++++++++-- TangemSdk/TangemSdkTests/JSONRPCTests.swift | 15 ++++++++ .../TangemSdkTests/Jsons/ImportWallet.json | 31 ++++++++++++++++ 10 files changed, 137 insertions(+), 32 deletions(-) create mode 100644 TangemSdk/TangemSdkTests/Jsons/ImportWallet.json diff --git a/Example/TangemSdkExample/AppModel.swift b/Example/TangemSdkExample/AppModel.swift index 9b8e21efa..64fc154e3 100644 --- a/Example/TangemSdkExample/AppModel.swift +++ b/Example/TangemSdkExample/AppModel.swift @@ -287,24 +287,30 @@ extension AppModel { return } - var seed: Data? = nil - - if !mnemonicString.isEmpty { - do { - let mnemonic = try Mnemonic(with: mnemonicString) - seed = try mnemonic.generateSeed() - } - catch { - self.complete(with: error) - return - } - } - tangemSdk.createWallet(curve: curve, cardId: cardId, - seed: seed, completion: handleCompletion) } + + func importWallet() { + guard let cardId = card?.cardId else { + self.complete(with: "Scan card to retrieve cardId") + return + } + + do { + let mnemonic = try Mnemonic(with: mnemonicString) + let seed = try mnemonic.generateSeed() + + tangemSdk.importWallet(curve: curve, + cardId: cardId, + seed: seed, + completion: handleCompletion) + } + catch { + self.complete(with: error) + } + } func purgeWallet(walletPublicKey: Data) { guard let cardId = card?.cardId else { @@ -667,6 +673,7 @@ extension AppModel { case setPasscode case resetUserCodes case createWallet + case importWallet case purgeWallet //files case readFiles @@ -703,6 +710,7 @@ extension AppModel { case .signHash: runWithPublicKey(signHash, walletPublicKey) case .signHashes: runWithPublicKey(signHashes, walletPublicKey) case .createWallet: createWallet() + case .importWallet: importWallet() case .purgeWallet: runWithPublicKey(purgeWallet, walletPublicKey) case .readFiles: readFiles() case .writeUserFile: writeUserFile() diff --git a/Example/TangemSdkExample/ContentView.swift b/Example/TangemSdkExample/ContentView.swift index 6504556f8..ac5bb62b0 100644 --- a/Example/TangemSdkExample/ContentView.swift +++ b/Example/TangemSdkExample/ContentView.swift @@ -125,7 +125,7 @@ struct ContentView: View { .cornerRadius(8) .overlay(RoundedRectangle(cornerRadius: 8) .stroke(Color.orange, lineWidth: 2)) - case .createWallet: + case .createWallet, .importWallet: VStack { Text("Create wallet configuration") .font(.headline) @@ -141,9 +141,11 @@ struct ContentView: View { .pickerStyle(SegmentedPickerStyle()) } - TextField("Optional mnemonic", text: $model.mnemonicString) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .autocapitalization(.none) + if case .importWallet = model.method { + TextField("Optional mnemonic", text: $model.mnemonicString) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .autocapitalization(.none) + } } .padding() .cornerRadius(8) diff --git a/TangemSdk/TangemSdk.xcodeproj/project.pbxproj b/TangemSdk/TangemSdk.xcodeproj/project.pbxproj index 5cc9afdc4..685843cdd 100644 --- a/TangemSdk/TangemSdk.xcodeproj/project.pbxproj +++ b/TangemSdk/TangemSdk.xcodeproj/project.pbxproj @@ -318,6 +318,7 @@ DC59CB0E29AF70C700EC14E1 /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59CB0D29AF70C700EC14E1 /* Mnemonic.swift */; }; DC8B0E3F286F221D009D64F7 /* BiometricsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8B0E3E286F221D009D64F7 /* BiometricsUtil.swift */; }; DCA9706628E35EAD0046E62E /* GenerateOTPCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA9706528E35EAD0046E62E /* GenerateOTPCommand.swift */; }; + DCE3281729D5DEE500AAC4AC /* ImportWallet.json in Resources */ = {isa = PBXBuildFile; fileRef = DCE3281629D5DEE500AAC4AC /* ImportWallet.json */; }; DCEA3ABC2875AEBA00B0B0DA /* BiometricsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEA3ABB2875AEBA00B0B0DA /* BiometricsStorage.swift */; }; DCEA3ABE2875AF0F00B0B0DA /* SecureStorageKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEA3ABD2875AF0F00B0B0DA /* SecureStorageKey.swift */; }; DCFCA17728F5629F0037586C /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFCA17628F5629F0037586C /* FocusableTextField.swift */; }; @@ -653,6 +654,7 @@ DC59CB0D29AF70C700EC14E1 /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = ""; }; DC8B0E3E286F221D009D64F7 /* BiometricsUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricsUtil.swift; sourceTree = ""; }; DCA9706528E35EAD0046E62E /* GenerateOTPCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateOTPCommand.swift; sourceTree = ""; }; + DCE3281629D5DEE500AAC4AC /* ImportWallet.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ImportWallet.json; sourceTree = ""; }; DCEA3ABB2875AEBA00B0B0DA /* BiometricsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricsStorage.swift; sourceTree = ""; }; DCEA3ABD2875AF0F00B0B0DA /* SecureStorageKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStorageKey.swift; sourceTree = ""; }; DCFCA17628F5629F0037586C /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = ""; }; @@ -834,6 +836,7 @@ 5D54408D268226B600F7D05B /* Depersonalize.json */, 5D54408F268226BC00F7D05B /* PurgeWallet.json */, 5D5440912682297A00F7D05B /* CreateWallet.json */, + DCE3281629D5DEE500AAC4AC /* ImportWallet.json */, 5D544093268243F700F7D05B /* Card.json */, ); path = Jsons; @@ -1660,6 +1663,7 @@ 5D2BDF8626DD4869002F7E19 /* TestParseRequest.json in Resources */, DC1244C329B766B70037BC05 /* mnemonic_valid_test_vectors.json in Resources */, 5D5440922682297A00F7D05B /* CreateWallet.json in Resources */, + DCE3281729D5DEE500AAC4AC /* ImportWallet.json in Resources */, 5D54408C2682269400F7D05B /* SetPasscode.json in Resources */, 5D4B127D26D3D351006E173C /* WriteFiles.json in Resources */, ); diff --git a/TangemSdk/TangemSdk/Common/JSON/Handlers.swift b/TangemSdk/TangemSdk/Common/JSON/Handlers.swift index 52850d23c..183efe360 100644 --- a/TangemSdk/TangemSdk/Common/JSON/Handlers.swift +++ b/TangemSdk/TangemSdk/Common/JSON/Handlers.swift @@ -58,10 +58,21 @@ class SignHashHandler: JSONRPCHandler { @available(iOS 13.0, *) class CreateWalletHandler: JSONRPCHandler { var method: String { "CREATE_WALLET" } - + + func makeRunnable(from parameters: [String : Any]) throws -> AnyJSONRPCRunnable { + let curve: EllipticCurve = try parameters.value(for: "curve") + let command = CreateWalletTask(curve: curve) + return command.eraseToAnyRunnable() + } +} + +@available(iOS 13.0, *) +class ImportWalletHandler: JSONRPCHandler { + var method: String { "IMPORT_WALLET" } + func makeRunnable(from parameters: [String : Any]) throws -> AnyJSONRPCRunnable { let curve: EllipticCurve = try parameters.value(for: "curve") - let seed: Data? = try parameters.value(for: "seed") + let seed: Data = try parameters.value(for: "seed") let command = CreateWalletTask(curve: curve, seed: seed) return command.eraseToAnyRunnable() } diff --git a/TangemSdk/TangemSdk/Common/JSON/JSONRPC.swift b/TangemSdk/TangemSdk/Common/JSON/JSONRPC.swift index cf3bb02e8..be84821de 100644 --- a/TangemSdk/TangemSdk/Common/JSON/JSONRPC.swift +++ b/TangemSdk/TangemSdk/Common/JSON/JSONRPC.swift @@ -28,6 +28,7 @@ public final class JSONRPCConverter { converter.register(ChangeFileSettingsHandler()) converter.register(DeriveWalletPublicKeyHandler()) converter.register(DeriveWalletPublicKeysHandler()) + converter.register(ImportWalletHandler()) return converter }() diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift index ea0cb26d4..df84d22c6 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift @@ -37,14 +37,17 @@ final class CreateWalletCommand: Command { /// Default initializer /// - Parameter curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card - /// - Parameter seed: An optional BIP39 seed to create wallet from. COS v6.10+. - init(curve: EllipticCurve, seed: Data?) throws { + init(curve: EllipticCurve) { self.curve = curve - if let seed { - self.privateKey = try BIP32().makeMasterKey(from: seed, curve: curve) - } else { - self.privateKey = nil - } + self.privateKey = nil + } + + /// Default initializer + /// - Parameter curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card + /// - Parameter seed: BIP39 seed to create wallet from. COS v6.10+. + init(curve: EllipticCurve, seed: Data) throws { + self.curve = curve + self.privateKey = try BIP32().makeMasterKey(from: seed, curve: curve) } deinit { diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift index 21ac10cce..3a2fa9733 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift @@ -22,10 +22,18 @@ public class CreateWalletTask: CardSessionRunnable { private let curve: EllipticCurve private let seed: Data? private var derivationTask: DeriveWalletPublicKeysTask? = nil + + /// Default initializer + /// - Parameter curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card + public init(curve: EllipticCurve) { + self.curve = curve + self.seed = nil + } + /// Default initializer /// - Parameter curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card - /// - Parameter seed: An optional BIP39 seed to create wallet from. COS v6.10+. Nil by default. - public init(curve: EllipticCurve, seed: Data? = nil) { + /// - Parameter seed: BIP39 seed to create wallet from. COS v6.10+. + public init(curve: EllipticCurve, seed: Data) { self.curve = curve self.seed = seed } @@ -36,7 +44,7 @@ public class CreateWalletTask: CardSessionRunnable { public func run(in session: CardSession, completion: @escaping CompletionResult) { do { - let command = try CreateWalletCommand(curve: curve, seed: seed) + let command = try makeCommand() command.run(in: session) { result in switch result { case .success(let response): @@ -123,4 +131,12 @@ public class CreateWalletTask: CardSessionRunnable { } } } + + private func makeCommand() throws -> CreateWalletCommand { + if let seed { + return try .init(curve: curve, seed: seed) + } + + return .init(curve: curve) + } } diff --git a/TangemSdk/TangemSdk/TangemSdk.swift b/TangemSdk/TangemSdk/TangemSdk.swift index 65c976099..5d7546d16 100644 --- a/TangemSdk/TangemSdk/TangemSdk.swift +++ b/TangemSdk/TangemSdk/TangemSdk.swift @@ -140,11 +140,25 @@ public extension TangemSdk { /// - curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card /// - initialMessage: A custom description that shows at the beginning of the NFC session. If nil, default message will be used /// - cardId: CID, Unique Tangem card ID number. - /// - seed: An optional BIP39 seed to create wallet from. COS v.6.10+. Nil by default. /// - completion: Returns `Swift.Result` func createWallet(curve: EllipticCurve, cardId: String, - seed: Data? = nil, + initialMessage: Message? = nil, + completion: @escaping CompletionResult) { + let command = CreateWalletTask(curve: curve) + startSession(with: command, cardId: cardId, initialMessage: initialMessage, completion: completion) + } + + /// This command will import an esisting wallet + /// - Parameters: + /// - curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card + /// - initialMessage: A custom description that shows at the beginning of the NFC session. If nil, default message will be used + /// - cardId: CID, Unique Tangem card ID number. + /// - seed: BIP39 seed to create wallet from. COS v.6.10+. + /// - completion: Returns `Swift.Result` + func importWallet(curve: EllipticCurve, + cardId: String, + seed: Data, initialMessage: Message? = nil, completion: @escaping CompletionResult) { let command = CreateWalletTask(curve: curve, seed: seed) diff --git a/TangemSdk/TangemSdkTests/JSONRPCTests.swift b/TangemSdk/TangemSdkTests/JSONRPCTests.swift index 6977ffec6..4254da6a3 100644 --- a/TangemSdk/TangemSdkTests/JSONRPCTests.swift +++ b/TangemSdk/TangemSdkTests/JSONRPCTests.swift @@ -139,6 +139,21 @@ class JSONRPCTests: XCTestCase { hasBackup: false)) testMethod(name: "CreateWallet", result: result) } + + func testImportWallet() { + let result = CreateWalletResponse(cardId: "c000111122223333", + wallet: Card.Wallet(publicKey: Data(hexString: "5130869115a2ff91959774c99d4dc2873f0c41af3e0bb23d027ab16d39de1348"), + chainCode: nil, + curve: .secp256r1, + settings: Card.Wallet.Settings(isPermanent: true), + totalSignedHashes: 10, + remainingSignatures: 100, + index: 1, + proof: nil, + isImported: false, + hasBackup: false)) + testMethod(name: "ImportWallet", result: result) + } func testPurgeWallet() { let result = PurgeWalletCommand.Response(cardId: "c000111122223333") diff --git a/TangemSdk/TangemSdkTests/Jsons/ImportWallet.json b/TangemSdk/TangemSdkTests/Jsons/ImportWallet.json new file mode 100644 index 000000000..cb9a90a3b --- /dev/null +++ b/TangemSdk/TangemSdkTests/Jsons/ImportWallet.json @@ -0,0 +1,31 @@ +{ + "request" : { + "jsonrpc": "2.0", + "id": 1, + "method": "import_wallet", + "params": { + "curve": "secp256r1", + "seed": "00000000000000000000000000000000" + } + }, + "response" : { + "jsonrpc" : "2.0", + "result" : { + "cardId": "c000111122223333", + "wallet": { + "publicKey": "5130869115a2ff91959774c99d4dc2873f0c41af3e0bb23d027ab16d39de1348", + "curve": "secp256r1", + "settings": { + "isPermanent" : true + }, + "totalSignedHashes": 10, + "remainingSignatures": 100, + "index": 1, + "hasBackup" : false, + "isImported": false, + "derivedKeys" : [] + } + }, + "id" : 1 + } +}