Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gh 716 push notifications #730

Merged
merged 8 commits into from Nov 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions Multisig.xcodeproj/project.pbxproj
Expand Up @@ -362,6 +362,11 @@
558836702565385F0014E8C7 /* RemoveSafeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5588366E2565385F0014E8C7 /* RemoveSafeCell.swift */; };
558836712565385F0014E8C7 /* RemoveSafeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5588366F2565385F0014E8C7 /* RemoveSafeCell.xib */; };
558F3CB224AB2C9500BBCDF1 /* RefreshableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558F3CB124AB2C9500BBCDF1 /* RefreshableScrollView.swift */; };
559670FF256E88FC0097D3A2 /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559670FE256E88FC0097D3A2 /* Signer.swift */; };
55967105256E92470097D3A2 /* SignerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55967104256E92470097D3A2 /* SignerTests.swift */; };
5596710D256E935F0097D3A2 /* MockSecureStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5596710C256E935F0097D3A2 /* MockSecureStore.swift */; };
55967119256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55967118256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift */; };
55967123256EB7690097D3A2 /* ConfirmationRequestNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55967122256EB7690097D3A2 /* ConfirmationRequestNotification.swift */; };
5596C03825507EAE00EF23A5 /* CollectibleTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5596C03725507EAE00EF23A5 /* CollectibleTableViewCell.xib */; };
5596C03E25507F3C00EF23A5 /* CollectibleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5596C03D25507F3C00EF23A5 /* CollectibleTableViewCell.swift */; };
5596C0482550813B00EF23A5 /* CollectiblesHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5596C0472550813B00EF23A5 /* CollectiblesHeaderView.swift */; };
Expand Down Expand Up @@ -867,6 +872,11 @@
5588366E2565385F0014E8C7 /* RemoveSafeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveSafeCell.swift; sourceTree = "<group>"; };
5588366F2565385F0014E8C7 /* RemoveSafeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RemoveSafeCell.xib; sourceTree = "<group>"; };
558F3CB124AB2C9500BBCDF1 /* RefreshableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshableScrollView.swift; sourceTree = "<group>"; };
559670FE256E88FC0097D3A2 /* Signer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = "<group>"; };
55967104256E92470097D3A2 /* SignerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignerTests.swift; sourceTree = "<group>"; };
5596710C256E935F0097D3A2 /* MockSecureStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSecureStore.swift; sourceTree = "<group>"; };
55967118256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterNotificationTokenRequestTests.swift; sourceTree = "<group>"; };
55967122256EB7690097D3A2 /* ConfirmationRequestNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationRequestNotification.swift; sourceTree = "<group>"; };
5596C03725507EAE00EF23A5 /* CollectibleTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollectibleTableViewCell.xib; sourceTree = "<group>"; };
5596C03D25507F3C00EF23A5 /* CollectibleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectibleTableViewCell.swift; sourceTree = "<group>"; };
5596C0472550813B00EF23A5 /* CollectiblesHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectiblesHeaderView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1208,6 +1218,7 @@
0A0EFCA1246EADD100D3D8BF /* Data */ = {
isa = PBXGroup;
children = (
55967116256EB1E50097D3A2 /* Services */,
5556A03924489F5D003EC861 /* Persistence */,
);
path = Data;
Expand Down Expand Up @@ -1808,6 +1819,7 @@
0AF2737024DC57FA007E4012 /* IncomingEtherNotification.swift */,
0AF2737224DC580D007E4012 /* IncomingTokenNotification.swift */,
0AF2736E24DC57E8007E4012 /* MultisigNotification.swift */,
55967122256EB7690097D3A2 /* ConfirmationRequestNotification.swift */,
);
path = Notifications;
sourceTree = "<group>";
Expand Down Expand Up @@ -1928,6 +1940,7 @@
55A585352501417E005E778B /* PrivateKey */,
0A0983A724BC5CC8009EE296 /* TokenRegistry */,
0A53071F254311C400E8A270 /* SafeTransactionSigner.swift */,
559670FE256E88FC0097D3A2 /* Signer.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -1973,6 +1986,7 @@
551DDEEA244F192300C719D3 /* Models */ = {
isa = PBXGroup;
children = (
5596710B256E933D0097D3A2 /* Mocks */,
555312F0250673840008206B /* Private Key */,
047300FD24781081004F1200 /* AddressTests.swift */,
551DDEEF244F1A0A00C719D3 /* SafeTests.swift */,
Expand All @@ -1986,6 +2000,7 @@
0A19D43C249A5BF100A316B6 /* Transaction */,
0A0983A824BC6222009EE296 /* InMemoryTokenStoreTests.swift */,
0A0983AA24BC638C009EE296 /* HardcodedTokenStoreTests.swift */,
55967104256E92470097D3A2 /* SignerTests.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -2094,6 +2109,30 @@
path = ReusableViews;
sourceTree = "<group>";
};
5596710B256E933D0097D3A2 /* Mocks */ = {
isa = PBXGroup;
children = (
5596710C256E935F0097D3A2 /* MockSecureStore.swift */,
);
path = Mocks;
sourceTree = "<group>";
};
55967116256EB1E50097D3A2 /* Services */ = {
isa = PBXGroup;
children = (
55967117256EB1F10097D3A2 /* Safe Transaction Service */,
);
path = Services;
sourceTree = "<group>";
};
55967117256EB1F10097D3A2 /* Safe Transaction Service */ = {
isa = PBXGroup;
children = (
55967118256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift */,
);
path = "Safe Transaction Service";
sourceTree = "<group>";
};
55A585352501417E005E778B /* PrivateKey */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2842,6 +2881,7 @@
0AC94458254068D800921CA5 /* NSNotification+Events.swift in Sources */,
0A0983A624BC5CA1009EE296 /* HardcodedTokenStore.swift in Sources */,
55CEFC2024939525003B2B19 /* TrackingEvent.swift in Sources */,
559670FF256E88FC0097D3A2 /* Signer.swift in Sources */,
0ADA911225656FEC0004DEC0 /* TransactionListViewController.swift in Sources */,
0ADDA20B24533F1E0066457E /* EnterSafeNameView.swift in Sources */,
0471D13224728C2C00B22A70 /* EmailLink.swift in Sources */,
Expand Down Expand Up @@ -2970,6 +3010,7 @@
files = (
0A0983AB24BC638C009EE296 /* HardcodedTokenStoreTests.swift in Sources */,
0A9BC33D24603AE800EB9C5D /* ENSTests.swift in Sources */,
5596710D256E935F0097D3A2 /* MockSecureStore.swift in Sources */,
0A9BC34C246053DB00EB9C5D /* DataInitWithHexTests.swift in Sources */,
5532D4CD2449A1980067505A /* LogServiceTests.swift in Sources */,
0A6430F5247ECB3E006FD30A /* ConfigurationKeyTests.swift in Sources */,
Expand All @@ -2983,8 +3024,10 @@
551DDEEE244F19AE00C719D3 /* CoreDataTestCase.swift in Sources */,
5532D4CB2449A1980067505A /* LoggableErrorTests.swift in Sources */,
55B66E8E24810BC800249E98 /* AppSettingsTests.swift in Sources */,
55967105256E92470097D3A2 /* SignerTests.swift in Sources */,
5532D4DE2449A34A0067505A /* TestUtils.swift in Sources */,
5556A03C24489F5D003EC861 /* CoreDataStackTests.swift in Sources */,
55967119256EB2120097D3A2 /* RegisterNotificationTokenRequestTests.swift in Sources */,
551DDEF0244F1A0A00C719D3 /* SafeTests.swift in Sources */,
5532D4CC2449A1980067505A /* LogFormatterTests.swift in Sources */,
0ACC4C4E247534EC00ADF201 /* GnosisSafeTests.swift in Sources */,
Expand Down Expand Up @@ -3019,6 +3062,7 @@
0AF2736B24DC57C6007E4012 /* NotificationPayload.swift in Sources */,
0AF2737524DC581A007E4012 /* ExecutedMultisigTransactionNotification.swift in Sources */,
0AF2736F24DC57E8007E4012 /* MultisigNotification.swift in Sources */,
55967123256EB7690097D3A2 /* ConfirmationRequestNotification.swift in Sources */,
0AF2736D24DC57D5007E4012 /* PlainAddress.swift in Sources */,
0AF2737124DC57FA007E4012 /* IncomingEtherNotification.swift in Sources */,
);
Expand Down
2 changes: 1 addition & 1 deletion Multisig/Cross-layer/App.swift
Expand Up @@ -24,7 +24,7 @@ class App {
// Data Layer
var coreDataStack: CoreDataProtocol = CoreDataStack()

let keychainService = KeychainService(identifier: App.configuration.app.bundleIdentifier)
var keychainService: SecureStore = KeychainService(identifier: App.configuration.app.bundleIdentifier)

// Services
let safeTransactionService = SafeTransactionService(
Expand Down
Expand Up @@ -7,27 +7,64 @@
//

import Foundation
import Web3

struct RegisterNotificationTokenRequest: JSONRequest {
let uuid: String?
let uuid: String
let safes: [String]
let cloudMessagingToken: String
let bundle: String
let version: String
let deviceType: String = "IOS"
let buildNumber: String
let timestamp: String?
let signatures: [String]?

var httpMethod: String { return "POST" }
var urlPath: String { return "/api/v1/notifications/devices/" }

typealias ResponseType = Response

init(deviceID: UUID? = nil, safes: [Address], token: String, bundle: String, version: String, buildNumber: String) {
self.uuid = deviceID?.uuidString.lowercased()
init(deviceID: String,
safes: [Address],
token: String,
bundle: String,
version: String,
buildNumber: String,
timestamp: String?) throws {

guard UUID(uuidString: deviceID) != nil else {
preconditionFailure("'deviceID' should be UUID string")
}
self.uuid = deviceID.lowercased()
self.safes = safes.map { $0.checksummed }
self.cloudMessagingToken = token
self.bundle = bundle
self.version = version
self.buildNumber = buildNumber
self.timestamp = timestamp

let string = [
"gnosis-safe",
self.uuid,
self.safes.joined(),
self.cloudMessagingToken,
self.bundle,
self.version,
self.deviceType,
self.buildNumber,
self.timestamp ?? ""
]
.joined()

if let signature = try? Signer.sign(string).value {
guard timestamp != nil else {
preconditionFailure("'timestamp' parameter is required if signing key exists")
}
self.signatures = [signature]
} else {
self.signatures = nil
DmitryBespalov marked this conversation as resolved.
Show resolved Hide resolved
}
}

struct Response: Decodable {
Expand All @@ -43,7 +80,22 @@ struct RegisterNotificationTokenRequest: JSONRequest {

extension SafeTransactionService {

func register(deviceID: UUID? = nil, safes: [Address], token: String, bundle: String, version: String, buildNumber: String) throws -> RegisterNotificationTokenRequest.Response {
return try execute(request: RegisterNotificationTokenRequest(deviceID: deviceID, safes: safes, token: token, bundle: bundle, version: version, buildNumber: buildNumber))
@discardableResult
func register(deviceID: String,
safes: [Address],
token: String,
bundle: String,
version: String,
buildNumber: String,
timestamp: String?) throws -> RegisterNotificationTokenRequest.Response {
return try execute(
request: try RegisterNotificationTokenRequest(deviceID: deviceID,
safes: safes,
token: token,
bundle: bundle,
version: version,
buildNumber: buildNumber,
timestamp: timestamp)
)
}
}
Expand Up @@ -17,9 +17,9 @@ struct UnregisterNotificationTokenRequest: JSONRequest {

typealias ResponseType = EmptyResponse

init(deviceID: UUID, address: Address) {
init(deviceID: String, address: Address) {
self.address = address.checksummed
self.deviceID = deviceID.uuidString.lowercased()
self.deviceID = deviceID.lowercased()
}

struct EmptyResponse: Decodable {
Expand All @@ -28,7 +28,7 @@ struct UnregisterNotificationTokenRequest: JSONRequest {
}

extension SafeTransactionService {
func unregister(deviceID: UUID, address: Address) throws {
func unregister(deviceID: String, address: Address) throws {
try execute(request: UnregisterNotificationTokenRequest(deviceID: deviceID, address: address))
}
}
Expand Up @@ -44,7 +44,7 @@ struct SignTransactionRequest: JSONRequest {
nonce = transaction.nonce.description
contractTransactionHash = transaction.safeTxHash!.description
let signature = try SafeTransactionSigner().sign(transaction, by: safeAddress)
self.sender = signature.sender
self.sender = signature.signer
self.signature = signature.value
}

Expand Down
18 changes: 3 additions & 15 deletions Multisig/Logic/Models/SafeTransactionSigner.swift
Expand Up @@ -11,26 +11,14 @@ import Web3

class SafeTransactionSigner {

struct Signature {
var value: String
var sender: String
}

func sign(_ transaction: Transaction, by safeAddress: Address) throws -> Signature {
func sign(_ transaction: Transaction, by safeAddress: Address) throws -> Signer.Signature {
let hashToSign = Data(ethHex: transaction.safeTxHash!.description)
let data = transaction.encodeTransactionData(for: AddressString(safeAddress))
guard EthHasher.hash(data) == hashToSign else {
throw "Invalid safeTxHash, please check the transaction data"
}
guard let pkData = try App.shared.keychainService.data(forKey: KeychainKey.ownerPrivateKey.rawValue) else {
throw "Private key not found"
}
let privateKey = try EthereumPrivateKey(pkData.bytes)
let eoaSignature = try privateKey.sign(hash: hashToSign.bytes)
let sender = privateKey.address.hex(eip55: true)
let v = String(eoaSignature.v + 27, radix: 16)
let safeSignature = "\(eoaSignature.r.toHexString())\(eoaSignature.s.toHexString())\(v)"
return Signature(value: safeSignature, sender: sender)

return try Signer.sign(hash: hashToSign)
}

class func numberOfKeysImported() -> Int {
Expand Down
46 changes: 46 additions & 0 deletions Multisig/Logic/Models/Signer.swift
@@ -0,0 +1,46 @@
//
// Signer.swift
// Multisig
//
// Created by Andrey Scherbovich on 25.11.20.
// Copyright © 2020 Gnosis Ltd. All rights reserved.
//

import Foundation
import Web3

class Signer {
struct Signature: Equatable {
var value: String
var signer: String
}

/// Signs the hash of the provided string with a stored private key.
/// Currently the app can store only one private key.
/// - Parameters:
/// - string: string to hash and sign
/// - Throws: errors during sisgning process
/// - Returns: Signature object containing hex(r) hex(s) hex(v + 27) as one strig of secp256k1 signature
static func sign(_ string: String) throws -> Signature {
let hash = EthHasher.hash(string.data(using: .utf8)!)
return try sign(hash: hash)
}

/// Signs the hash with a stored private key by provided address.
/// Currently the app can store only one private key.
/// - Parameters:
/// - hash: hash to sign
/// - Throws: errors during sisgning process
/// - Returns: Signature object containing hex(r) hex(s) hex(v + 27) as one strig of secp256k1 signature
static func sign(hash: Data) throws -> Signature {
guard let pkData = try App.shared.keychainService.data(forKey: KeychainKey.ownerPrivateKey.rawValue) else {
throw "Private key not found"
}
let privateKey = try EthereumPrivateKey(pkData.bytes)
let signer = privateKey.address.hex(eip55: true)
let eoaSignature = try privateKey.sign(hash: hash.bytes)
let v = String(eoaSignature.v + 27, radix: 16)
let signature = "\(eoaSignature.r.toHexString())\(eoaSignature.s.toHexString())\(v)"
return Signature(value: signature, signer: signer)
}
}