Skip to content

Commit

Permalink
Merge pull request #14 from soramitsu/feature/qr-decoding
Browse files Browse the repository at this point in the history
add qr code encoding/decoding
  • Loading branch information
ERussel authored Oct 29, 2020
2 parents 6116358 + 6909145 commit a61e770
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 20 deletions.
20 changes: 20 additions & 0 deletions Example/FearlessUtils.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
84DA3B4324C8CE5A00B5E27F /* ScaleStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DA3B3924C8CE5900B5E27F /* ScaleStringTests.swift */; };
84DA3B4424C8CE5A00B5E27F /* ScaleInt16Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DA3B3A24C8CE5900B5E27F /* ScaleInt16Tests.swift */; };
84DA3B4524C8CE5A00B5E27F /* ScaleUInt8Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DA3B3B24C8CE5900B5E27F /* ScaleUInt8Tests.swift */; };
84ED7256254A001A006102DF /* SubstrateQREncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ED7255254A001A006102DF /* SubstrateQREncoderTests.swift */; };
84ED725C254A0233006102DF /* SubstrateQRDecoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ED725B254A0233006102DF /* SubstrateQRDecoderTests.swift */; };
A0FDCFB5B61F738FEE34A5E6 /* Pods_FearlessUtils_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16D2E77D7010BEBF43DFE08E /* Pods_FearlessUtils_Tests.framework */; };
B2BF1BE136637C8404B79450 /* Pods_FearlessUtils_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9A63E44DBCDCE738F798358 /* Pods_FearlessUtils_Example.framework */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -89,6 +91,8 @@
84DA3B3A24C8CE5900B5E27F /* ScaleInt16Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleInt16Tests.swift; sourceTree = "<group>"; };
84DA3B3B24C8CE5900B5E27F /* ScaleUInt8Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleUInt8Tests.swift; sourceTree = "<group>"; };
84DA3B4824C8CEEF00B5E27F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Tests/Info.plist; sourceTree = SOURCE_ROOT; };
84ED7255254A001A006102DF /* SubstrateQREncoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubstrateQREncoderTests.swift; sourceTree = "<group>"; };
84ED725B254A0233006102DF /* SubstrateQRDecoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubstrateQRDecoderTests.swift; sourceTree = "<group>"; };
93F24F5EEC204E4B0F918C39 /* Pods-FearlessUtils_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FearlessUtils_Example.debug.xcconfig"; path = "Target Support Files/Pods-FearlessUtils_Example/Pods-FearlessUtils_Example.debug.xcconfig"; sourceTree = "<group>"; };
B82F3EE26AAEE44C6E7F9962 /* Pods-FearlessUtils_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FearlessUtils_Tests.release.xcconfig"; path = "Target Support Files/Pods-FearlessUtils_Tests/Pods-FearlessUtils_Tests.release.xcconfig"; sourceTree = "<group>"; };
B9A63E44DBCDCE738F798358 /* Pods_FearlessUtils_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FearlessUtils_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -236,6 +240,7 @@
84DA3B3024C8CE5900B5E27F /* Tests */ = {
isa = PBXGroup;
children = (
84ED7254254A0006006102DF /* QR */,
8467FD2324E71A0E005D486C /* Icon */,
84CB472224DBE81100837E11 /* Keystore */,
842D1E7424D0066100C30A7A /* Resources */,
Expand Down Expand Up @@ -268,6 +273,15 @@
path = Scale;
sourceTree = "<group>";
};
84ED7254254A0006006102DF /* QR */ = {
isa = PBXGroup;
children = (
84ED7255254A001A006102DF /* SubstrateQREncoderTests.swift */,
84ED725B254A0233006102DF /* SubstrateQRDecoderTests.swift */,
);
path = QR;
sourceTree = "<group>";
};
A78EB2A0158FDFCA62F39CAC /* Pods */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -499,6 +513,8 @@
842D1E2324C98CEA00C30A7A /* StorageKeyFactoryTests.swift in Sources */,
8467FD2824E72CCD005D486C /* HSLColorTests.swift in Sources */,
842D1E2724C9B63300C30A7A /* ScaleOptionTests.swift in Sources */,
84ED725C254A0233006102DF /* SubstrateQRDecoderTests.swift in Sources */,
84ED7256254A001A006102DF /* SubstrateQREncoderTests.swift in Sources */,
842D1E7324CFF4EC00C30A7A /* KeypairDeriviationTests.swift in Sources */,
84CB472424DBE82C00837E11 /* KeystoreExtractorTests.swift in Sources */,
842D1E7124CFF31800C30A7A /* KeypairDeriviation.swift in Sources */,
Expand Down Expand Up @@ -636,6 +652,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = YLWWUD25VZ;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
INFOPLIST_FILE = FearlessUtils/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MODULE_NAME = ExampleApp;
Expand All @@ -652,6 +669,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = YLWWUD25VZ;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
INFOPLIST_FILE = FearlessUtils/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MODULE_NAME = ExampleApp;
Expand All @@ -667,6 +685,7 @@
baseConfigurationReference = 7BCE5EB68B0A48835979E64A /* Pods-FearlessUtils_Tests.debug.xcconfig */;
buildSettings = {
DEVELOPMENT_TEAM = YLWWUD25VZ;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
FRAMEWORK_SEARCH_PATHS = (
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
"$(inherited)",
Expand All @@ -690,6 +709,7 @@
baseConfigurationReference = B82F3EE26AAEE44C6E7F9962 /* Pods-FearlessUtils_Tests.release.xcconfig */;
buildSettings = {
DEVELOPMENT_TEAM = YLWWUD25VZ;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
FRAMEWORK_SEARCH_PATHS = (
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
"$(inherited)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
BuildableName = "FearlessUtils_Example.app"
BlueprintName = "FearlessUtils_Example"
ReferencedContainer = "container:FearlessUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
Expand All @@ -54,23 +62,11 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
BuildableName = "FearlessUtils_Example.app"
BlueprintName = "FearlessUtils_Example"
ReferencedContainer = "container:FearlessUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand All @@ -87,8 +83,6 @@
ReferencedContainer = "container:FearlessUtils.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
6 changes: 6 additions & 0 deletions Example/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ target 'FearlessUtils_Example' do

end
end

post_install do |installer|
installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
end
end
8 changes: 4 additions & 4 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PODS:
- BigInt (5.0.0)
- FearlessUtils (0.5.0):
- FearlessUtils (0.8.0):
- BigInt (~> 5.0)
- IrohaCrypto/ed25519 (~> 0.7.0)
- IrohaCrypto/Scrypt (~> 0.7.0)
Expand Down Expand Up @@ -44,12 +44,12 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
BigInt: 74b4d88367b0e819d9f77393549226d36faeb0d8
FearlessUtils: 8cec323272fe5dd811996b2c7c50587b857946d0
FearlessUtils: d4e6e36e45ce2fb48c6981dba2192f1f6c7183eb
IrohaCrypto: bddf15865c4df189b9c5273ba9c675afae8b7f4c
scrypt.c: b42ae06183251329d2b2c620c226fb541a4a3592
TweetNacl: 3abf4d1d2082b0114e7a67410e300892448951e6
xxHash-Swift: 30bd6a7507b3b7348a277c49b1cb6346c2905ec7

PODFILE CHECKSUM: 002fb4b06641c82f822cb8ddc9d08ba3f79755d6
PODFILE CHECKSUM: 933d053287c20439760037c9b2fdaf8abafb515f

COCOAPODS: 1.9.3
COCOAPODS: 1.10.0
6 changes: 5 additions & 1 deletion FearlessUtils.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@

Pod::Spec.new do |s|
s.name = 'FearlessUtils'
s.version = '0.7.0'
s.version = '0.8.0'
s.summary = 'Utility library that implements clients specific logic to interact with substrate based networks'

s.homepage = 'https://github.com/soramitsu/fearless-utils-iOS'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'ERussel' => 'rezin@soramitsu.co.jp' }
s.source = { :git => 'https://github.com/soramitsu/fearless-utils-iOS.git', :tag => s.version.to_s }
s.swift_version = '5.0'

s.ios.deployment_target = '11.0'

s.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64'}
s.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }

s.source_files = 'FearlessUtils/Classes/**/*'
s.dependency 'IrohaCrypto/sr25519', '~> 0.7.0'
s.dependency 'IrohaCrypto/ed25519', '~> 0.7.0'
Expand Down
11 changes: 11 additions & 0 deletions FearlessUtils/Classes/Common/Data+AccountId.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

extension Data {
func matchAccountId(_ accountId: Data) -> Bool {
if accountId == self {
return true
}

return accountId == (try? self.blake2b32())
}
}
42 changes: 42 additions & 0 deletions FearlessUtils/Classes/QR/SubstrateQRCommon.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation

public struct SubstrateQRInfo: Equatable {
public let prefix: String
public let address: String
public let rawPublicKey: Data
public let username: String?

public init(prefix: String = SubstrateQR.prefix,
address: String,
rawPublicKey: Data,
username: String?) {
self.prefix = prefix
self.address = address
self.rawPublicKey = rawPublicKey
self.username = username
}
}

public protocol SubstrateQREncodable {
func encode(info: SubstrateQRInfo) throws -> Data
}

public protocol SubstrateQRDecodable {
func decode(data: Data) throws -> SubstrateQRInfo
}

public enum SubstrateQREncoderError: Error, Equatable {
case brokenData
}

public enum SubstrateQRDecoderError: Error, Equatable {
case brokenFormat
case unexpectedNumberOfFields
case undefinedPrefix
case accountIdMismatch
}

public struct SubstrateQR {
public static let prefix: String = "substrate"
public static let fieldsSeparator: String = ":"
}
48 changes: 48 additions & 0 deletions FearlessUtils/Classes/QR/SubstrateQRDecoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation
import IrohaCrypto

open class SubstrateQRDecoder: SubstrateQRDecodable {
public let networkType: SNAddressType
public let separator: String
public let prefix: String

private lazy var addressFactory = SS58AddressFactory()

public init(networkType: SNAddressType,
prefix: String = SubstrateQR.prefix,
separator: String = SubstrateQR.fieldsSeparator) {
self.prefix = prefix
self.networkType = networkType
self.separator = separator
}

public func decode(data: Data) throws -> SubstrateQRInfo {
guard let fields = String(data: data, encoding: .utf8)?
.components(separatedBy: separator) else {
throw SubstrateQRDecoderError.brokenFormat
}

guard fields.count >= 3, fields.count <= 4 else {
throw SubstrateQRDecoderError.unexpectedNumberOfFields
}

guard fields[0] == prefix else {
throw SubstrateQRDecoderError.undefinedPrefix
}

let address = fields[1]
let accountId = try addressFactory.accountId(fromAddress: address, type: networkType)
let publicKey = try Data(hexString: fields[2])

guard publicKey.matchAccountId(accountId) else {
throw SubstrateQRDecoderError.accountIdMismatch
}

let username = fields.count > 3 ? fields[3] : nil

return SubstrateQRInfo(prefix: prefix,
address: address,
rawPublicKey: publicKey,
username: username)
}
}
28 changes: 28 additions & 0 deletions FearlessUtils/Classes/QR/SubstrateQREncoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation
import IrohaCrypto

open class SubstrateQREncoder: SubstrateQREncodable {
let separator: String

public init(separator: String = SubstrateQR.fieldsSeparator) {
self.separator = separator
}

public func encode(info: SubstrateQRInfo) throws -> Data {
var fields: [String] = [
info.prefix,
info.address,
info.rawPublicKey.toHex(includePrefix: true)
]

if let username = info.username {
fields.append(username)
}

guard let data = fields.joined(separator: separator).data(using: .utf8) else {
throw SubstrateQREncoderError.brokenData
}

return data
}
}
67 changes: 67 additions & 0 deletions Tests/QR/SubstrateQRDecoderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import XCTest
import FearlessUtils

class SubstrateQRDecoderTests: XCTestCase {
func testSuccessfullDecodingWithName() throws {
let data = try Data(hexString: "7375627374726174653a354633794b66354b7651343978356b346e5258624157674a485368516a51544245655a3950754e646d6562614e7837343a3078383432353834306636633337306637663264383239316333653639303436376530613866626238623036343866633964363439356439396463613763376133663a425642")

let publicKey = try Data(hexString: "0x8425840f6c370f7f2d8291c3e690467e0a8fbb8b0648fc9d6495d99dca7c7a3f")
let expectedInfo = SubstrateQRInfo(prefix: SubstrateQR.prefix,
address: "5F3yKf5KvQ49x5k4nRXbAWgJHShQjQTBEeZ9PuNdmebaNx74",
rawPublicKey: publicKey,
username: "BVB")

let decodedInfo = try SubstrateQRDecoder(networkType: .genericSubstrate).decode(data: data)

XCTAssertEqual(decodedInfo, expectedInfo)
}

func testSuccessfullDecodingWithoutName() throws {
let data = try Data(hexString: "7375627374726174653a354633794b66354b7651343978356b346e5258624157674a485368516a51544245655a3950754e646d6562614e7837343a307838343235383430663663333730663766326438323931633365363930343637653061386662623862303634386663396436343935643939646361376337613366")

let publicKey = try Data(hexString: "0x8425840f6c370f7f2d8291c3e690467e0a8fbb8b0648fc9d6495d99dca7c7a3f")
let expectedInfo = SubstrateQRInfo(prefix: SubstrateQR.prefix,
address: "5F3yKf5KvQ49x5k4nRXbAWgJHShQjQTBEeZ9PuNdmebaNx74",
rawPublicKey: publicKey,
username: nil)

let decodedInfo = try SubstrateQRDecoder(networkType: .genericSubstrate).decode(data: data)

XCTAssertEqual(decodedInfo, expectedInfo)
}

func testUndefinedPrefix() throws {
let data = try Data(hexString: "73756273747261743a354633794b66354b7651343978356b346e5258624157674a485368516a51544245655a3950754e646d6562614e7837343a307838343235383430663663333730663766326438323931633365363930343637653061386662623862303634386663396436343935643939646361376337613366")

perforErrorTest(data, expectedError: .undefinedPrefix)
}

func testUnexpectedNumberOfFields() throws {
let data = try Data(hexString: "7375627374726174653a354633794b66354b7651343978356b346e5258624157674a485368516a51544245655a3950754e646d6562614e7837343a3078383432353834306636633337306637663264383239316333653639303436376530613866626238623036343866633964363439356439396463613763376133663a31323a313233")

perforErrorTest(data, expectedError: .unexpectedNumberOfFields)
}

func testAccountIdMismatch() throws {
let data = try Data(hexString: "7375627374726174653a354633794b66354b7651343978356b346e5258624157674a485368516a51544245655a3950754e646d6562614e7837343a3078383432353834306636633337306637663364383239316333653639303436376530613866626238623036343866633964363439356439396463613763376133663a425642")

perforErrorTest(data, expectedError: .accountIdMismatch)
}

// MARK: Private

private func perforErrorTest(_ data: Data, expectedError: SubstrateQRDecoderError) {
do {
_ = try SubstrateQRDecoder(networkType: .genericSubstrate).decode(data: data)

XCTFail("Exception expected")
} catch {
guard let decodingError = error as? SubstrateQRDecoderError else {
XCTFail("Decoding error expected")
return
}

XCTAssertEqual(decodingError, expectedError)
}
}
}
Loading

0 comments on commit a61e770

Please sign in to comment.