Skip to content

Commit

Permalink
Merge pull request #262 from tangem/IOS-3219_create_wallet_from_seed
Browse files Browse the repository at this point in the history
Create wallet from the seed
  • Loading branch information
tureck1y authored Apr 4, 2023
2 parents e2604c2 + acde40d commit 6a731aa
Show file tree
Hide file tree
Showing 26 changed files with 310 additions and 34 deletions.
25 changes: 24 additions & 1 deletion Example/TangemSdkExample/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class AppModel: ObservableObject {

//Wallet creation
@Published var curve: EllipticCurve = .secp256k1
@Published var mnemonicString: String = ""
//Sign
@Published var derivationPath: String = ""
//Attestation
Expand Down Expand Up @@ -285,11 +286,31 @@ extension AppModel {
self.complete(with: "Scan card to retrieve cardId")
return
}

tangemSdk.createWallet(curve: curve,
cardId: cardId,
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 {
Expand Down Expand Up @@ -652,6 +673,7 @@ extension AppModel {
case setPasscode
case resetUserCodes
case createWallet
case importWallet
case purgeWallet
//files
case readFiles
Expand Down Expand Up @@ -688,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()
Expand Down
8 changes: 7 additions & 1 deletion Example/TangemSdkExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -140,6 +140,12 @@ struct ContentView: View {
}
.pickerStyle(SegmentedPickerStyle())
}

if case .importWallet = model.method {
TextField("Optional mnemonic", text: $model.mnemonicString)
.textFieldStyle(RoundedBorderTextFieldStyle())
.autocapitalization(.none)
}
}
.padding()
.cornerRadius(8)
Expand Down
1 change: 1 addition & 0 deletions Example/TangemSdkExample/Developer/StaticData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ extension AppModel {
"disableFiles": false,
"allowHDWallets": true,
"allowBackup": true,
"allowKeysImport": true,
"NDEF": [],
"cardData": {
"date": "2021-03-15",
Expand Down
4 changes: 4 additions & 0 deletions TangemSdk/TangemSdk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -653,6 +654,7 @@
DC59CB0D29AF70C700EC14E1 /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = "<group>"; };
DC8B0E3E286F221D009D64F7 /* BiometricsUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricsUtil.swift; sourceTree = "<group>"; };
DCA9706528E35EAD0046E62E /* GenerateOTPCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateOTPCommand.swift; sourceTree = "<group>"; };
DCE3281629D5DEE500AAC4AC /* ImportWallet.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ImportWallet.json; sourceTree = "<group>"; };
DCEA3ABB2875AEBA00B0B0DA /* BiometricsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricsStorage.swift; sourceTree = "<group>"; };
DCEA3ABD2875AF0F00B0B0DA /* SecureStorageKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStorageKey.swift; sourceTree = "<group>"; };
DCFCA17628F5629F0037586C /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -834,6 +836,7 @@
5D54408D268226B600F7D05B /* Depersonalize.json */,
5D54408F268226BC00F7D05B /* PurgeWallet.json */,
5D5440912682297A00F7D05B /* CreateWallet.json */,
DCE3281629D5DEE500AAC4AC /* ImportWallet.json */,
5D544093268243F700F7D05B /* Card.json */,
);
path = Jsons;
Expand Down Expand Up @@ -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 */,
);
Expand Down
3 changes: 3 additions & 0 deletions TangemSdk/TangemSdk/Common/APDU/StatusWord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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? {
Expand All @@ -54,6 +55,8 @@ public enum StatusWord: UInt16 {
return TangemSdkError.accessCodeRequired
case .invalidPascode:
return TangemSdkError.passcodeRequired
case .walletAlreadyExists:
return TangemSdkError.walletAlreadyCreated
default:
return nil
}
Expand Down
4 changes: 4 additions & 0 deletions TangemSdk/TangemSdk/Common/Card/CardSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public extension Card {
public let isHDWalletAllowed: Bool
/// Is allowed to create backup
public let isBackupAllowed: 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
Expand Down Expand Up @@ -71,6 +73,7 @@ extension Card.Settings {
self.isHDWalletAllowed = mask.contains(.allowHDWallets)
self.isFilesAllowed = !mask.contains(.disableFiles)
self.isBackupAllowed = mask.contains(.allowBackup)
self.isKeysImportAllowed = mask.contains(.allowKeysImport)

var encryptionModes: [EncryptionMode] = [.strong]
if mask.contains(.allowFastEncryption) {
Expand Down Expand Up @@ -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 allowKeysImport = CardSettingsMask(rawValue: 0x00800000)
}

//MARK:- CardSettingsMask OptionSetCodable conformance
Expand Down
2 changes: 2 additions & 0 deletions TangemSdk/TangemSdk/Common/Card/FirmwareVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
/// Keys import support
static let keysImportAvailable = FirmwareVersion(major: 6, minor: 11)
/// Tmp range for visa cards
static let visaRange = 5.25...5.30
}
Expand Down
40 changes: 38 additions & 2 deletions TangemSdk/TangemSdk/Common/Card/Wallet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public extension Card {
public let index: Int
/// Proof for BLS Proof of possession scheme (POP)
public let proof: Data?
/// 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`
Expand All @@ -56,9 +58,43 @@ 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 backed up
case backedUpImported = 0xC2
}
}

@available(iOS 13.0, *)
extension Card.Wallet.Status {
var isBackedUp: Bool {
switch self {
case .backedUp, .backedUpAndPurged, .backedUpImported:
return true
default:
return false
}
}

var isImported: Bool {
switch self {
case .imported, .backedUpImported:
return true
default:
return false
}
}

var isAvailable: Bool {
switch self {
case .empty, .purged, .backedUpAndPurged:
return false
default:
return true
}
}
}

Expand Down
8 changes: 6 additions & 2 deletions TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable {
case walletNotFound
case cardWithMaxZeroWallets
case walletCannotBeCreated
case walletAlreadyCreated

// MARK: Backup errors
case backupFailedCardNotLinked
Expand All @@ -264,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
Expand All @@ -278,6 +279,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable {
//MARK: Settings
case filesDisabled
case hdWalletDisabled
case keysImportDisabled

case resetPinNoCardToReset
case resetPinWrongCard(internalCode: Int? = nil)
Expand Down Expand Up @@ -343,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
Expand Down Expand Up @@ -379,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
Expand All @@ -399,6 +402,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable {

case .filesDisabled: return 42002
case .hdWalletDisabled: return 42003
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ struct CardDeserializer {
remainingSignatures: remainingSignatures,
index: 0,
proof: nil,
isImported: false,
hasBackup: false)

wallets.append(wallet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.isAvailable {
throw TangemSdkError.walletNotFound
}

Expand All @@ -64,6 +65,7 @@ class WalletDeserializer {
remainingSignatures: nil,
index: try decoder.decode(.walletIndex),
proof: try decoder.decode(.proof),
hasBackup: status == .backuped)
isImported: status.isImported,
hasBackup: status.isBackedUp)
}
}
14 changes: 13 additions & 1 deletion TangemSdk/TangemSdk/Common/JSON/Handlers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,26 @@ 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 command = CreateWalletTask(curve: curve, seed: seed)
return command.eraseToAnyRunnable()
}
}

@available(iOS 13.0, *)
class PurgeWalletHandler: JSONRPCHandler {
var method: String { "PURGE_WALLET" }
Expand Down
1 change: 1 addition & 0 deletions TangemSdk/TangemSdk/Common/JSON/JSONRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public final class JSONRPCConverter {
converter.register(ChangeFileSettingsHandler())
converter.register(DeriveWalletPublicKeyHandler())
converter.register(DeriveWalletPublicKeysHandler())
converter.register(ImportWalletHandler())
return converter
}()

Expand Down
1 change: 1 addition & 0 deletions TangemSdk/TangemSdk/Common/TLV/TlvTag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public enum TlvTag: Byte {
// MARK: - HDWallet
case walletHDPath = 0x6A
case walletHDChain = 0x6B
case walletPrivateKey = 0x6F

// MARK: - Backup
case certificate = 0x55
Expand Down
8 changes: 4 additions & 4 deletions TangemSdk/TangemSdk/Crypto/BIP39/BIP39.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions TangemSdk/TangemSdk/Crypto/CryptoUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 6a731aa

Please sign in to comment.