diff --git a/Example/TangemSdkExample/AppModel.swift b/Example/TangemSdkExample/AppModel.swift index 7bb52ac2a..70bcb035d 100644 --- a/Example/TangemSdkExample/AppModel.swift +++ b/Example/TangemSdkExample/AppModel.swift @@ -18,6 +18,7 @@ class AppModel: ObservableObject { //Wallet creation @Published var curve: EllipticCurve = .secp256k1 @Published var mnemonicString: String = "" + @Published var passphrase: String = "" //Sign @Published var derivationPath: String = "" @Published var signHashesCount: String = "15" @@ -306,18 +307,11 @@ extension AppModel { 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) - } + tangemSdk.importWallet(curve: curve, + cardId: cardId, + mnemonic: mnemonicString, + passphrase: passphrase, + completion: handleCompletion) } func purgeWallet(walletPublicKey: Data) { diff --git a/Example/TangemSdkExample/ContentView.swift b/Example/TangemSdkExample/ContentView.swift index a261ec1af..7c028f1ec 100644 --- a/Example/TangemSdkExample/ContentView.swift +++ b/Example/TangemSdkExample/ContentView.swift @@ -145,6 +145,10 @@ struct ContentView: View { TextField("Optional mnemonic", text: $model.mnemonicString) .textFieldStyle(RoundedBorderTextFieldStyle()) .autocapitalization(.none) + + TextField("Optional passphrase", text: $model.passphrase) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .autocapitalization(.none) } } .padding() diff --git a/TangemSdk/TangemSdk.xcodeproj/project.pbxproj b/TangemSdk/TangemSdk.xcodeproj/project.pbxproj index 806ba6f32..d8932ff70 100644 --- a/TangemSdk/TangemSdk.xcodeproj/project.pbxproj +++ b/TangemSdk/TangemSdk.xcodeproj/project.pbxproj @@ -312,6 +312,7 @@ DC1244E629BB8E580037BC05 /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1244E529BB8E580037BC05 /* NetworkType.swift */; }; DC1244E829BB9E0C0037BC05 /* ExtendedKeySerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1244E729BB9E0C0037BC05 /* ExtendedKeySerializer.swift */; }; DC22228729D431AB001129F8 /* SetUserCodeRecoveryAllowed.json in Resources */ = {isa = PBXBuildFile; fileRef = DC22228629D431AB001129F8 /* SetUserCodeRecoveryAllowed.json */; }; + DC234CC629F1A3F100082063 /* ImportWalletMnemonic.json in Resources */ = {isa = PBXBuildFile; fileRef = DC234CC529F1A3F100082063 /* ImportWalletMnemonic.json */; }; DC4E442929BF42630088617C /* Base58Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4E442829BF42630088617C /* Base58Tests.swift */; }; DC59CB0429AF597900EC14E1 /* Wordlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59CB0329AF597900EC14E1 /* Wordlist.swift */; }; DC59CB0A29AF6F9C00EC14E1 /* EntropyLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59CB0929AF6F9C00EC14E1 /* EntropyLength.swift */; }; @@ -323,7 +324,7 @@ DCC0A21429D3201300C45B13 /* SetUserCodeRecoveryAllowedTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC0A21329D3201300C45B13 /* SetUserCodeRecoveryAllowedTask.swift */; }; DCC0A21629D3216100C45B13 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC0A21529D3216100C45B13 /* UserSettings.swift */; }; DCC6A64829D212E8007BA5B7 /* GetEntropyCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC6A64729D212E8007BA5B7 /* GetEntropyCommand.swift */; }; - DCE3281729D5DEE500AAC4AC /* ImportWallet.json in Resources */ = {isa = PBXBuildFile; fileRef = DCE3281629D5DEE500AAC4AC /* ImportWallet.json */; }; + DCE3281729D5DEE500AAC4AC /* ImportWalletSeed.json in Resources */ = {isa = PBXBuildFile; fileRef = DCE3281629D5DEE500AAC4AC /* ImportWalletSeed.json */; }; DCEA3ABC2875AEBA00B0B0DA /* BiometricsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEA3ABB2875AEBA00B0B0DA /* BiometricsStorage.swift */; }; DCEA3ABE2875AF0F00B0B0DA /* SecureStorageKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEA3ABD2875AF0F00B0B0DA /* SecureStorageKey.swift */; }; DCF6188429F069DB001BE133 /* AttestCardKey.json in Resources */ = {isa = PBXBuildFile; fileRef = DCF6188329F069DB001BE133 /* AttestCardKey.json */; }; @@ -654,6 +655,7 @@ DC1244E529BB8E580037BC05 /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = ""; }; DC1244E729BB9E0C0037BC05 /* ExtendedKeySerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedKeySerializer.swift; sourceTree = ""; }; DC22228629D431AB001129F8 /* SetUserCodeRecoveryAllowed.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = SetUserCodeRecoveryAllowed.json; sourceTree = ""; }; + DC234CC529F1A3F100082063 /* ImportWalletMnemonic.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ImportWalletMnemonic.json; sourceTree = ""; }; DC4E442829BF42630088617C /* Base58Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base58Tests.swift; sourceTree = ""; }; DC59CB0329AF597900EC14E1 /* Wordlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wordlist.swift; sourceTree = ""; }; DC59CB0929AF6F9C00EC14E1 /* EntropyLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntropyLength.swift; sourceTree = ""; }; @@ -665,7 +667,7 @@ DCC0A21329D3201300C45B13 /* SetUserCodeRecoveryAllowedTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetUserCodeRecoveryAllowedTask.swift; sourceTree = ""; }; DCC0A21529D3216100C45B13 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; DCC6A64729D212E8007BA5B7 /* GetEntropyCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetEntropyCommand.swift; sourceTree = ""; }; - DCE3281629D5DEE500AAC4AC /* ImportWallet.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ImportWallet.json; sourceTree = ""; }; + DCE3281629D5DEE500AAC4AC /* ImportWalletSeed.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ImportWalletSeed.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 = ""; }; DCF6188329F069DB001BE133 /* AttestCardKey.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AttestCardKey.json; sourceTree = ""; }; @@ -849,7 +851,8 @@ 5D54408D268226B600F7D05B /* Depersonalize.json */, 5D54408F268226BC00F7D05B /* PurgeWallet.json */, 5D5440912682297A00F7D05B /* CreateWallet.json */, - DCE3281629D5DEE500AAC4AC /* ImportWallet.json */, + DCE3281629D5DEE500AAC4AC /* ImportWalletSeed.json */, + DC234CC529F1A3F100082063 /* ImportWalletMnemonic.json */, DCF6188329F069DB001BE133 /* AttestCardKey.json */, 5D544093268243F700F7D05B /* Card.json */, ); @@ -1682,6 +1685,7 @@ 5D38D06E26790B1A0052F67C /* Scan.json in Resources */, DCF6188429F069DB001BE133 /* AttestCardKey.json in Resources */, 5D4B127B26D3D32E006E173C /* ChangeFileSettings.json in Resources */, + DC234CC629F1A3F100082063 /* ImportWalletMnemonic.json in Resources */, 5D4B127926D3CF4F006E173C /* DeleteFiles.json in Resources */, 5D544094268243F700F7D05B /* Card.json in Resources */, 5D5440982682497100F7D05B /* v4.json in Resources */, @@ -1691,7 +1695,7 @@ 5D2BDF8626DD4869002F7E19 /* TestParseRequest.json in Resources */, DC1244C329B766B70037BC05 /* mnemonic_valid_test_vectors.json in Resources */, 5D5440922682297A00F7D05B /* CreateWallet.json in Resources */, - DCE3281729D5DEE500AAC4AC /* ImportWallet.json in Resources */, + DCE3281729D5DEE500AAC4AC /* ImportWalletSeed.json in Resources */, 5D54408C2682269400F7D05B /* SetPasscode.json in Resources */, 5D4B127D26D3D351006E173C /* WriteFiles.json in Resources */, ); diff --git a/TangemSdk/TangemSdk/Common/Card/CardSettings.swift b/TangemSdk/TangemSdk/Common/Card/CardSettings.swift index a98fd07f0..6629227ca 100644 --- a/TangemSdk/TangemSdk/Common/Card/CardSettings.swift +++ b/TangemSdk/TangemSdk/Common/Card/CardSettings.swift @@ -32,7 +32,7 @@ public extension Card { public let isHDWalletAllowed: Bool /// Is allowed to create backup public let isBackupAllowed: Bool - /// Is allowed to import keys. COS. v6.11+ + /// Is allowed to import keys. COS. v6.16+ public let isKeysImportAllowed: Bool /// Is allowed to delete wallet. COS before v4 @SkipEncoding diff --git a/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift b/TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift index 9df89ebe5..9a23535ce 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: 11) + static let keysImportAvailable = FirmwareVersion(major: 6, minor: 16) /// Tmp range for visa cards static let visaRange = 5.25...5.30 } diff --git a/TangemSdk/TangemSdk/Common/JSON/Handlers.swift b/TangemSdk/TangemSdk/Common/JSON/Handlers.swift index 0f362e8b3..d78e26be2 100644 --- a/TangemSdk/TangemSdk/Common/JSON/Handlers.swift +++ b/TangemSdk/TangemSdk/Common/JSON/Handlers.swift @@ -72,7 +72,19 @@ class ImportWalletHandler: JSONRPCHandler { 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 seedParam: Data? = try parameters.value(for: "seed") + + let mnemonicString: String? = try parameters.value(for: "mnemonic") + let passphrase: String = try parameters.value(for: "passphrase") ?? "" + let seedFromMnemonic = try mnemonicString.map { try Mnemonic(with: $0).generateSeed(with: passphrase) } + + let seed: Data? = seedParam ?? seedFromMnemonic + guard let seed else { + throw JSONRPCError(.invalidParams, + data: JSONRPCErrorData(.invalidParams, message: "You should pass a seed or a mnemonic and an optional passphrase")) + } + let command = CreateWalletTask(curve: curve, seed: seed) return command.eraseToAnyRunnable() } diff --git a/TangemSdk/TangemSdk/Operations/Attestation/AttestWalletKeyCommand.swift b/TangemSdk/TangemSdk/Operations/Attestation/AttestWalletKeyCommand.swift index 4ac4e1a97..8611f7f67 100644 --- a/TangemSdk/TangemSdk/Operations/Attestation/AttestWalletKeyCommand.swift +++ b/TangemSdk/TangemSdk/Operations/Attestation/AttestWalletKeyCommand.swift @@ -19,14 +19,14 @@ public struct AttestWalletKeyResponse: JSONStringConvertible { public let walletSignature: Data /// Challenge, used to check wallet public let challenge: Data - /// Confirmation signature of the wallet ownership. COS: 2.01+. And a wallet's status for COS: 6.11+. + /// Confirmation signature of the wallet ownership. COS: 2.01+. And a wallet's status for COS: 6.16+. /// - `ConfirmationMode.none` : No signature will be returned. - /// - `ConfirmationMode.static` : Wallet's public key and wallet's status (COS 6.11+) signed with the card's private key. - /// - `ConfirmationMode.dynamic`: Wallet's public key, wallet's status (COS 6.11+), `challenge`, and `publicKeySalt`, signed with the card's private key. + /// - `ConfirmationMode.static` : Wallet's public key and wallet's status (COS 6.16+) signed with the card's private key. + /// - `ConfirmationMode.dynamic`: Wallet's public key, wallet's status (COS 6.16+), `challenge`, and `publicKeySalt`, signed with the card's private key. public let cardSignature: Data? /// Optional random salt, generated by the card for `dynamic` `confirmationMode`. COS: 2.01+. public let publicKeySalt: Data? - /// Status of the wallet. COS v.6.11+ + /// Status of the wallet. COS v.6.16+ public let walletStatus: Card.Wallet.Status? /// Counter of `AttestWalletKey` command executions. A very big value of this counter may indicate a hacking attempts. COS: 2.01+. let counter: Int? diff --git a/TangemSdk/TangemSdk/Operations/UserSettings/SetUserSettingsCommand.swift b/TangemSdk/TangemSdk/Operations/UserSettings/SetUserSettingsCommand.swift index 0000a18c9..8bef143c5 100644 --- a/TangemSdk/TangemSdk/Operations/UserSettings/SetUserSettingsCommand.swift +++ b/TangemSdk/TangemSdk/Operations/UserSettings/SetUserSettingsCommand.swift @@ -8,7 +8,7 @@ import Foundation -/// Deserialized response from the Tangem card after `SetUserSettingsCommand`. COS v.6.11+ +/// Deserialized response from the Tangem card after `SetUserSettingsCommand`. COS v.6.16+ @available(iOS 13.0, *) struct SetUserSettingsCommandResponse: JSONStringConvertible { /// Unique Tangem card ID number. @@ -17,7 +17,7 @@ struct SetUserSettingsCommandResponse: JSONStringConvertible { let settings: Card.UserSettings } -/// Set user serrings on a card. COS v.6.11+ +/// Set user serrings on a card. COS v.6.16+ @available(iOS 13.0, *) class SetUserSettingsCommand: Command { var preflightReadMode: PreflightReadMode { .readCardOnly } diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift index 462741fb5..31fe682ba 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletCommand.swift @@ -42,7 +42,7 @@ final class CreateWalletCommand: Command { self.privateKey = nil } - /// Use this initializer to import a key from the seed. COS v6.11+. + /// Use this initializer to import a key from the seed. COS v6.16+. /// - Parameter curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card /// - Parameter seed: BIP39 seed to create wallet from. init(curve: EllipticCurve, seed: Data) throws { diff --git a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift index cf16241c4..de60bb42e 100644 --- a/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift +++ b/TangemSdk/TangemSdk/Operations/Wallet/CreateWalletTask.swift @@ -30,7 +30,7 @@ public class CreateWalletTask: CardSessionRunnable { self.seed = nil } - /// Use this initializer to import a key from the seed. COS v6.11+. + /// Use this initializer to import a key from the seed. COS v6.16+. /// - Parameter curve: Elliptic curve of the wallet. `Card.supportedCurves` contains all curves supported by the card /// - Parameter seed: BIP39 seed to create wallet from. public init(curve: EllipticCurve, seed: Data) { diff --git a/TangemSdk/TangemSdk/TangemSdk.swift b/TangemSdk/TangemSdk/TangemSdk.swift index b8fce72a9..6ac944b1f 100644 --- a/TangemSdk/TangemSdk/TangemSdk.swift +++ b/TangemSdk/TangemSdk/TangemSdk.swift @@ -171,7 +171,7 @@ 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: BIP39 seed to create wallet from. COS v.6.11+. + /// - seed: BIP39 seed to create wallet from. COS v.6.16+. /// - completion: Returns `Swift.Result` func importWallet(curve: EllipticCurve, cardId: String, @@ -181,6 +181,29 @@ public extension TangemSdk { let command = CreateWalletTask(curve: curve, seed: seed) 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. + /// - mnemonic: BIP39 mnemonic to create wallet from. COS v.6.16+. + /// - passphrase: BIP39 passphrase to create wallet from. COS v.6.16+. Empty passphrase by default. + /// - completion: Returns `Swift.Result` + func importWallet(curve: EllipticCurve, + cardId: String, + mnemonic: String, + passphrase: String = "", + initialMessage: Message? = nil, + completion: @escaping CompletionResult) { + do { + let seed = try Mnemonic(with: mnemonic).generateSeed(with: passphrase) + let command = CreateWalletTask(curve: curve, seed: seed) + startSession(with: command, cardId: cardId, initialMessage: initialMessage, completion: completion) + } catch { + completion(.failure(error.toTangemSdkError())) + } + } /// This command deletes all wallet data. If Is_Reusable flag is enabled during personalization, /// the card changes state to ‘Empty’ and a new wallet can be created by `CREATE_WALLET` command. diff --git a/TangemSdk/TangemSdkTests/JSONRPCTests.swift b/TangemSdk/TangemSdkTests/JSONRPCTests.swift index 452244324..aeef9d798 100644 --- a/TangemSdk/TangemSdkTests/JSONRPCTests.swift +++ b/TangemSdk/TangemSdkTests/JSONRPCTests.swift @@ -140,7 +140,7 @@ class JSONRPCTests: XCTestCase { testMethod(name: "CreateWallet", result: result) } - func testImportWallet() { + func testImportWalletSeed() { let result = CreateWalletResponse(cardId: "c000111122223333", wallet: Card.Wallet(publicKey: Data(hexString: "5130869115a2ff91959774c99d4dc2873f0c41af3e0bb23d027ab16d39de1348"), chainCode: nil, @@ -152,7 +152,22 @@ class JSONRPCTests: XCTestCase { proof: nil, isImported: false, hasBackup: false)) - testMethod(name: "ImportWallet", result: result) + testMethod(name: "ImportWalletSeed", result: result) + } + + func testImportWalletMnemonic() { + let result = CreateWalletResponse(cardId: "c000111122223333", + wallet: Card.Wallet(publicKey: Data(hexString: "029983A77B155ED3B3B9E1DDD223BD5AA073834C8F61113B2F1B883AAA70971B5F"), + chainCode: Data(hexString: "C7A888C4C670406E7AAEB6E86555CE0C4E738A337F9A9BC239F6D7E475110A4E"), + curve: .secp256k1, + settings: Card.Wallet.Settings(isPermanent: true), + totalSignedHashes: 10, + remainingSignatures: 100, + index: 1, + proof: nil, + isImported: false, + hasBackup: false)) + testMethod(name: "ImportWalletMnemonic", result: result) } func testPurgeWallet() { diff --git a/TangemSdk/TangemSdkTests/Jsons/ImportWalletMnemonic.json b/TangemSdk/TangemSdkTests/Jsons/ImportWalletMnemonic.json new file mode 100644 index 000000000..d93946966 --- /dev/null +++ b/TangemSdk/TangemSdkTests/Jsons/ImportWalletMnemonic.json @@ -0,0 +1,33 @@ +{ + "request" : { + "jsonrpc": "2.0", + "id": 1, + "method": "import_wallet", + "params": { + "curve": "secp256k1", + "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "passphrase": "TREZOR" + } + }, + "response" : { + "jsonrpc" : "2.0", + "result" : { + "cardId": "c000111122223333", + "wallet": { + "publicKey": "029983A77B155ED3B3B9E1DDD223BD5AA073834C8F61113B2F1B883AAA70971B5F", + "chainCode": "C7A888C4C670406E7AAEB6E86555CE0C4E738A337F9A9BC239F6D7E475110A4E", + "curve": "secp256k1", + "settings": { + "isPermanent" : true + }, + "totalSignedHashes": 10, + "remainingSignatures": 100, + "index": 1, + "hasBackup" : false, + "isImported": false, + "derivedKeys" : [] + } + }, + "id" : 1 + } +} diff --git a/TangemSdk/TangemSdkTests/Jsons/ImportWallet.json b/TangemSdk/TangemSdkTests/Jsons/ImportWalletSeed.json similarity index 100% rename from TangemSdk/TangemSdkTests/Jsons/ImportWallet.json rename to TangemSdk/TangemSdkTests/Jsons/ImportWalletSeed.json