Skip to content

Commit 7c6f60d

Browse files
authored
IOS-8008 Sign hashes with different sizes (#382)
1 parent 17ab8cc commit 7c6f60d

File tree

7 files changed

+376
-74
lines changed

7 files changed

+376
-74
lines changed

TangemSdk/TangemSdk.xcodeproj/project.pbxproj

+16
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,10 @@
288288
DC70AD652A80FC9F00928836 /* CommonFirmwareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC70AD642A80FC9F00928836 /* CommonFirmwareTests.swift */; };
289289
DC70AD672A8115BB00928836 /* FWTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC70AD662A8115BB00928836 /* FWTestCase.swift */; };
290290
DC7254902A03E20A0003FE1B /* DerivedKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72548F2A03E20A0003FE1B /* DerivedKeys.swift */; };
291+
DC77F24D2CD42610001B2929 /* ChunkHashesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC77F24C2CD4260A001B2929 /* ChunkHashesTests.swift */; };
292+
DC77F24F2CD426B6001B2929 /* ChunkedHashesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC77F24E2CD426B6001B2929 /* ChunkedHashesContainer.swift */; };
293+
DC77F2512CD426E3001B2929 /* SignDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC77F2502CD426E3001B2929 /* SignDTO.swift */; };
294+
DC77F2532CD430A3001B2929 /* ChunkHashesUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC77F2522CD43099001B2929 /* ChunkHashesUtil.swift */; };
291295
DC8B0E3F286F221D009D64F7 /* BiometricsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8B0E3E286F221D009D64F7 /* BiometricsUtil.swift */; };
292296
DCA9706628E35EAD0046E62E /* GenerateOTPCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA9706528E35EAD0046E62E /* GenerateOTPCommand.swift */; };
293297
DCACA0402CB51FF400A3DD51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DCACA03F2CB51FF400A3DD51 /* Assets.xcassets */; };
@@ -679,6 +683,10 @@
679683
DC70AD642A80FC9F00928836 /* CommonFirmwareTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonFirmwareTests.swift; sourceTree = "<group>"; };
680684
DC70AD662A8115BB00928836 /* FWTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FWTestCase.swift; sourceTree = "<group>"; };
681685
DC72548F2A03E20A0003FE1B /* DerivedKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DerivedKeys.swift; sourceTree = "<group>"; };
686+
DC77F24C2CD4260A001B2929 /* ChunkHashesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChunkHashesTests.swift; sourceTree = "<group>"; };
687+
DC77F24E2CD426B6001B2929 /* ChunkedHashesContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChunkedHashesContainer.swift; sourceTree = "<group>"; };
688+
DC77F2502CD426E3001B2929 /* SignDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignDTO.swift; sourceTree = "<group>"; };
689+
DC77F2522CD43099001B2929 /* ChunkHashesUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChunkHashesUtil.swift; sourceTree = "<group>"; };
682690
DC8B0E3E286F221D009D64F7 /* BiometricsUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricsUtil.swift; sourceTree = "<group>"; };
683691
DCA9706528E35EAD0046E62E /* GenerateOTPCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateOTPCommand.swift; sourceTree = "<group>"; };
684692
DCACA03F2CB51FF400A3DD51 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -986,7 +994,10 @@
986994
5D46F156268105A500DC6447 /* Sign */ = {
987995
isa = PBXGroup;
988996
children = (
997+
DC77F2522CD43099001B2929 /* ChunkHashesUtil.swift */,
998+
DC77F24E2CD426B6001B2929 /* ChunkedHashesContainer.swift */,
989999
5D6A92E42345F2B200158457 /* SignCommand.swift */,
1000+
DC77F2502CD426E3001B2929 /* SignDTO.swift */,
9901001
5D46F1542681032B00DC6447 /* SignHashCommand.swift */,
9911002
5D46F157268105BF00DC6447 /* SignHashesCommand.swift */,
9921003
);
@@ -1290,6 +1301,7 @@
12901301
DC0665562A7AC8F500CFFCC6 /* Ed25519Slip0010Tests.swift */,
12911302
DC70AD642A80FC9F00928836 /* CommonFirmwareTests.swift */,
12921303
DC70AD662A8115BB00928836 /* FWTestCase.swift */,
1304+
DC77F24C2CD4260A001B2929 /* ChunkHashesTests.swift */,
12931305
);
12941306
path = TangemSdkTests;
12951307
sourceTree = "<group>";
@@ -2052,6 +2064,7 @@
20522064
5D6A92E72345F2D600158457 /* AttestWalletKeyCommand.swift in Sources */,
20532065
5D73FC2926B8140200DF1BB4 /* DerivationPath.swift in Sources */,
20542066
B06EBBC12534794100B0FEEA /* ChangeFileSettingsCommand.swift in Sources */,
2067+
DC77F24F2CD426B6001B2929 /* ChunkedHashesContainer.swift in Sources */,
20552068
5DDD6C6C25D30B0D00E48D7B /* SuccessResponse.swift in Sources */,
20562069
DC59CB0429AF597900EC14E1 /* Wordlist.swift in Sources */,
20572070
5D8666622731687A0095CC82 /* ResetCodesViewModel.swift in Sources */,
@@ -2108,6 +2121,7 @@
21082121
5DA5B618233E124A0058C720 /* StatusWord.swift in Sources */,
21092122
5D866658273163BB0095CC82 /* BaseViewDelegate.swift in Sources */,
21102123
B0A9447B256546DE00A7958E /* Card.swift in Sources */,
2124+
DC77F2512CD426E3001B2929 /* SignDTO.swift in Sources */,
21112125
DCFCA17728F5629F0037586C /* FocusableTextField.swift in Sources */,
21122126
B06EBBC525347B7800B0FEEA /* ChangeFileSettingsTask.swift in Sources */,
21132127
5D0F8D0226C6A80F002E84A4 /* UserCodeHeaderView.swift in Sources */,
@@ -2168,6 +2182,7 @@
21682182
5D705B5B23DAF2BB002CCD7A /* Config.swift in Sources */,
21692183
DC612D722AFD60C2005A547F /* SessionFilter.swift in Sources */,
21702184
5D6A92EC2346069700158457 /* TangemSdk.swift in Sources */,
2185+
DC77F2532CD430A3001B2929 /* ChunkHashesUtil.swift in Sources */,
21712186
DC1244B329B60B6F0037BC05 /* BIP39.swift in Sources */,
21722187
5DFFC49F233B9D69004964E8 /* NFCReader.swift in Sources */,
21732188
5DE43A6626D515B100ECA36A /* FinalizePrimaryCardTask.swift in Sources */,
@@ -2209,6 +2224,7 @@
22092224
DC1244C929B778750037BC05 /* BIP32Tests.swift in Sources */,
22102225
DC70AD652A80FC9F00928836 /* CommonFirmwareTests.swift in Sources */,
22112226
DC3D980A2A792804001EEE7A /* KeysImportTests.swift in Sources */,
2227+
DC77F24D2CD42610001B2929 /* ChunkHashesTests.swift in Sources */,
22122228
DC1244E429BB806E0037BC05 /* WIFTests.swift in Sources */,
22132229
DC1244B929B610550037BC05 /* BIP39Tests.swift in Sources */,
22142230
5DD127A224F3D1A0009ACA29 /* JsonTests.swift in Sources */,

TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift

+1-5
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable {
125125

126126
/// This error is returned when a `SignCommand` receives only empty hashes for signature.
127127
case emptyHashes
128-
129-
/// This error is returned when a `SignCommand` receives hashes of different lengths for signature.
130-
case hashSizeMustBeEqual
131-
128+
132129
case signHashesNotAvailable
133130

134131
// Write Extra Issuer Data Errors
@@ -370,7 +367,6 @@ public enum TangemSdkError: Error, LocalizedError, Encodable {
370367

371368
case .noRemainingSignatures: return 40901
372369
case .emptyHashes: return 40902
373-
case .hashSizeMustBeEqual: return 40903
374370
case .signHashesNotAvailable: return 40905
375371
case .oldCard: return 40907
376372

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// ChunkHashesUtil.swift
3+
// TangemSdk
4+
//
5+
// Created by Alexander Osokin on 31.10.2024.
6+
// Copyright © 2024 Tangem AG. All rights reserved.
7+
//
8+
9+
struct ChunkHashesUtil {
10+
func chunkHashes(_ hashes: [Data]) -> [Chunk] {
11+
let hashes = hashes.enumerated().map { Hash(index: $0.offset, data: $0.element) }
12+
let hashesBySize = Dictionary(grouping: hashes, by: { $0.data.count })
13+
14+
let chunks = hashesBySize.flatMap { hashesGroup in
15+
let hashSize = hashesGroup.key
16+
let chunkSize = getChunkSize(for: hashSize)
17+
18+
let chunkedHashes = hashesGroup.value.chunked(into: chunkSize)
19+
let chunks = chunkedHashes.map { Chunk(hashSize: hashSize, hashes: $0) }
20+
21+
return chunks
22+
}
23+
24+
return chunks
25+
}
26+
27+
func getChunkSize(for hashSize: Int) -> Int {
28+
/// These devices are not able to sign long hashes.
29+
if NFCUtils.isPoorNfcQualityDevice {
30+
return Constants.maxChunkSizePoorNfcQualityDevice
31+
}
32+
33+
guard hashSize > 0 else {
34+
return Constants.maxChunkSize
35+
}
36+
37+
let estimatedChunkSize = Constants.packageSize / hashSize
38+
let chunkSize = max(1, min(estimatedChunkSize, Constants.maxChunkSize))
39+
return chunkSize
40+
}
41+
}
42+
43+
// MARK: - Constants
44+
45+
private extension ChunkHashesUtil {
46+
enum Constants {
47+
/// The max answer is 1152 bytes (unencrypted) and 1120 (encrypted). The worst case is 8 hashes * 64 bytes for ed + 512 bytes of signatures + cardId, SignedHashes + TLV + SW is ok.
48+
static let packageSize = 512
49+
50+
/// Card limitation
51+
static let maxChunkSize = 10
52+
53+
/// Empirical value
54+
static let maxChunkSizePoorNfcQualityDevice = 2
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// ChunkedHashesContainer.swift
3+
// TangemSdk
4+
//
5+
// Created by Alexander Osokin on 31.10.2024.
6+
// Copyright © 2024 Tangem AG. All rights reserved.
7+
//
8+
9+
10+
import Foundation
11+
12+
struct ChunkedHashesContainer {
13+
var isEmpty: Bool { chunks.isEmpty }
14+
let chunksCount: Int
15+
16+
private(set) var currentChunkIndex: Int = 0
17+
18+
private let chunks: [Chunk]
19+
private var signedChunks: [SignedChunk] = []
20+
21+
init(hashes: [Data]) {
22+
self.chunks = ChunkHashesUtil().chunkHashes(hashes)
23+
self.chunksCount = chunks.count
24+
}
25+
26+
func getCurrentChunk() throws -> Chunk {
27+
guard currentChunkIndex < chunks.count else {
28+
throw ChunkedHashesContainerError.processingError
29+
}
30+
31+
return chunks[currentChunkIndex]
32+
}
33+
34+
mutating func addSignedChunk(_ signedChunk: SignedChunk) {
35+
signedChunks.append(signedChunk)
36+
currentChunkIndex += 1
37+
}
38+
39+
func getSignatures() -> [Data] {
40+
let signedHashes = signedChunks.flatMap { $0.signedHashes }.sorted()
41+
let signatures = signedHashes.map { $0.signature }
42+
return signatures
43+
}
44+
}
45+
46+
// MARK: - ChunkedHashesContainerError
47+
48+
enum ChunkedHashesContainerError: Error {
49+
case processingError
50+
}
51+

0 commit comments

Comments
 (0)