Fix: Make EIP712v support for WalletConnect work correctly

pull/2437/head
Hwee-Boon Yar 4 years ago
parent 131254da01
commit 473acfebc2
  1. 6
      AlphaWallet/Browser/Coordinators/DappBrowserCoordinator.swift
  2. 12
      AlphaWallet/Browser/Types/DappAction.swift
  3. 25
      AlphaWallet/Browser/Types/DappCommand.swift
  4. 2
      AlphaWallet/KeyManagement/EtherKeystore.swift
  5. 6
      AlphaWallet/Tokens/Views/TokenInstanceWebView.swift
  6. 4
      AlphaWallet/Transfer/Coordinators/SignMessageCoordinator.swift
  7. 6
      AlphaWallet/Transfer/ViewModels/ConfirmSignMessageViewControllerViewModel.swift
  8. 1
      AlphaWallet/Vendors/TrustCore/ABIValue.swift
  9. 21
      AlphaWallet/WalletConnect/Coordinator/WalletConnectCoordinator.swift
  10. 62
      AlphaWallet/WalletConnect/Types/EIP712TypedData.swift
  11. 33
      AlphaWallet/WalletConnect/Types/WalletConnectActionType.swift
  12. 6
      AlphaWallet/WalletConnect/Types/WalletConnectServerRequest.swift
  13. 4
      AlphaWallet/WalletConnect/WalletConnectServer.swift

@ -197,8 +197,8 @@ final class DappBrowserCoordinator: NSObject, Coordinator {
callback = DappCallback(id: callbackID, value: .signPersonalMessage(data))
case .typedMessage:
callback = DappCallback(id: callbackID, value: .signTypedMessage(data))
case .eip712:
callback = DappCallback(id: callbackID, value: .signTypedMessage(data))
case .eip712v3:
callback = DappCallback(id: callbackID, value: .signTypedMessageV3(data))
}
strongSelf.browserViewController.notifyFinish(callbackID: callbackID, value: .success(callback))
case .failure:
@ -419,6 +419,8 @@ extension DappBrowserCoordinator: BrowserViewControllerDelegate {
signMessage(with: .personalMessage(Data(hex: msg)), account: account, callbackID: callbackID)
case .signTypedMessage(let typedData):
signMessage(with: .typedMessage(typedData), account: account, callbackID: callbackID)
case .signTypedMessageV3(let typedData):
signMessage(with: .eip712v3(typedData), account: account, callbackID: callbackID)
case .unknown, .sendRawTransaction:
break
}

@ -8,6 +8,7 @@ enum DappAction {
case signMessage(String)
case signPersonalMessage(String)
case signTypedMessage([EthTypedData])
case signTypedMessageV3(EIP712TypedData)
case signTransaction(UnconfirmedTransaction)
case sendTransaction(UnconfirmedTransaction)
case sendRawTransaction(String)
@ -28,8 +29,15 @@ extension DappAction {
let data = command.object["data"]?.value ?? ""
return .signPersonalMessage(data)
case .signTypedMessage:
let array = command.object["data"]?.array ?? []
return .signTypedMessage(array)
if let data = command.object["data"] {
if let eip712Data = data.eip712v3Data {
return .signTypedMessageV3(eip712Data)
} else {
return .signTypedMessage(data.eip712PreV3Array)
}
} else {
return .signTypedMessage([])
}
case .unknown:
return .unknown
}

@ -19,6 +19,7 @@ enum DappCallbackValue {
case signMessage(Data)
case signPersonalMessage(Data)
case signTypedMessage(Data)
case signTypedMessageV3(Data)
var object: String {
switch self {
@ -32,24 +33,36 @@ enum DappCallbackValue {
return data.hexEncoded
case .signTypedMessage(let data):
return data.hexEncoded
case .signTypedMessageV3(let data):
return data.hexEncoded
}
}
}
struct DappCommandObjectValue: Decodable {
public var value: String = ""
public var array: [EthTypedData] = []
public init(from coder: Decoder) throws {
var value: String = ""
var eip712PreV3Array: [EthTypedData] = []
let eip712v3Data: EIP712TypedData?
init(from coder: Decoder) throws {
let container = try coder.singleValueContainer()
if let intValue = try? container.decode(Int.self) {
self.value = String(intValue)
value = String(intValue)
eip712v3Data = nil
} else if let stringValue = try? container.decode(String.self) {
self.value = stringValue
if let data = stringValue.data(using: .utf8), let object = try? JSONDecoder().decode(EIP712TypedData.self, from: data) {
value = ""
eip712v3Data = object
} else {
value = stringValue
eip712v3Data = nil
}
} else {
var arrayContainer = try coder.unkeyedContainer()
while !arrayContainer.isAtEnd {
self.array.append(try arrayContainer.decode(EthTypedData.self))
eip712PreV3Array.append(try arrayContainer.decode(EthTypedData.self))
}
eip712v3Data = nil
}
}
}

@ -480,7 +480,7 @@ open class EtherKeystore: Keystore {
}
func signEip712TypedData(_ data: EIP712TypedData, for account: AlphaWallet.Address) -> Result<Data, KeystoreError> {
return signHash(data.signHash, for: account)
signHash(data.digest, for: account)
}
func signTypedMessage(_ datas: [EthTypedData], for account: AlphaWallet.Address) -> Result<Data, KeystoreError> {

@ -442,7 +442,7 @@ extension TokenInstanceWebView: WKScriptMessageHandler {
let msg = convertMessageToHex(msg: hexMessage)
let callbackID = command.id
signMessage(with: .personalMessage(Data(hex: msg)), account: account, callbackID: callbackID)
case .signTransaction, .sendTransaction, .signMessage, .signTypedMessage, .unknown, .sendRawTransaction:
case .signTransaction, .sendTransaction, .signMessage, .signTypedMessage, .unknown, .sendRawTransaction, .signTypedMessageV3:
return
}
case .watch:
@ -500,8 +500,8 @@ extension TokenInstanceWebView {
callback = DappCallback(id: callbackID, value: .signPersonalMessage(data))
case .typedMessage:
callback = DappCallback(id: callbackID, value: .signTypedMessage(data))
case .eip712:
callback = DappCallback(id: callbackID, value: .signTypedMessage(data))
case .eip712v3:
callback = DappCallback(id: callbackID, value: .signTypedMessageV3(data))
}
strongSelf.notifyFinish(callbackID: callbackID, value: .success(callback))
case .failure:

@ -9,7 +9,7 @@ enum SignMessageType {
case message(Data)
case personalMessage(Data)
case typedMessage([EthTypedData])
case eip712(EIP712TypedData)
case eip712v3(EIP712TypedData)
}
protocol SignMessageCoordinatorDelegate: class {
@ -64,7 +64,7 @@ class SignMessageCoordinator: Coordinator {
} else {
result = keystore.signTypedMessage(typedData, for: account)
}
case .eip712(let data):
case .eip712v3(let data):
result = keystore.signEip712TypedData(data, for: account)
}
switch result {

@ -80,14 +80,14 @@ struct ConfirmSignMessageViewControllerViewModel {
return message
case .typedMessage:
return nil
case .eip712(let data):
case .eip712v3(let data):
return data.rawStringValue
}
}
var typedMessagesCount: Int {
switch message {
case .message, .eip712, .personalMessage:
case .message, .eip712v3, .personalMessage:
return 0
case .typedMessage(let typedMessage):
return typedMessage.count
@ -96,7 +96,7 @@ struct ConfirmSignMessageViewControllerViewModel {
private func typedMessage(at index: Int) -> EthTypedData? {
switch message {
case .message, .eip712, .personalMessage:
case .message, .eip712v3, .personalMessage:
return nil
case .typedMessage(let typedMessage):
if index < typedMessage.count {

@ -16,7 +16,6 @@ public indirect enum ABIValue: Equatable {
case int(bits: Int, BigInt)
/// Address, similar to `uint(bits: 160)`
//TODO change this to use AlphaWallet.Address?
case address(Address)
//TODO eventually replace `address` with this

@ -112,8 +112,8 @@ extension WalletConnectCoordinator: WalletConnectServerDelegate {
return self.signMessage(with: .message(hexMessage.toHexData), account: account, callbackID: action.id, url: action.url)
case .signPersonalMessage(let hexMessage):
return self.signMessage(with: .personalMessage(hexMessage.toHexData), account: account, callbackID: action.id, url: action.url)
case .signTypedMessage(let typedData):
return self.signMessage(with: .eip712(typedData), account: account, callbackID: action.id, url: action.url)
case .signTypedMessageV3(let typedData):
return self.signMessage(with: .eip712v3(typedData), account: account, callbackID: action.id, url: action.url)
case .sendRawTransaction(let raw):
return self.sendRawTransaction(session: session, rawTransaction: raw, callbackID: action.id, url: action.url)
case .getTransactionCount:
@ -132,14 +132,7 @@ extension WalletConnectCoordinator: WalletConnectServerDelegate {
firstly {
SignMessageCoordinator.promise(navigationController, keystore: keystore, coordinator: self, signType: type, account: account)
}.map { data -> WalletConnectServer.Callback in
switch type {
case .message:
return .init(id: id, url: url, value: .signMessage(data))
case .personalMessage:
return .init(id: id, url: url, value: .signPersonalMessage(data))
case .typedMessage, .eip712:
return .init(id: id, url: url, value: .signTypedMessage(data))
}
return .init(id: id, url: url, value: data)
}
}
@ -152,10 +145,10 @@ extension WalletConnectCoordinator: WalletConnectServerDelegate {
}.map { data -> WalletConnectServer.Callback in
switch data {
case .signedTransaction(let data):
return .init(id: id, url: url, value: .signTransaction(data))
return .init(id: id, url: url, value: data)
case .sentTransaction(let transaction):
let data = Data(hex: transaction.id)
return .init(id: id, url: url, value: .sentTransaction(data))
return .init(id: id, url: url, value: data)
case .sentRawTransaction:
//NOTE: Doesn't support sentRawTransaction for TransactionConfirmationCoordinator, for it we are using another function
throw PMKError.cancelled
@ -204,7 +197,7 @@ extension WalletConnectCoordinator: WalletConnectServerDelegate {
throw PMKError.cancelled
case .sentRawTransaction(let transactionId, _):
let data = Data(hex: transactionId)
return .init(id: id, url: url, value: .sentTransaction(data))
return .init(id: id, url: url, value: data)
}
}.then { callback -> Promise<WalletConnectServer.Callback> in
return self.showFeedbackOnSuccess(callback)
@ -216,7 +209,7 @@ extension WalletConnectCoordinator: WalletConnectServerDelegate {
GetNextNonce(server: session.server, wallet: session.account.address).promise()
}.map {
if let data = Data(fromHexEncodedString: String(format:"%02X", $0)) {
return .init(id: id, url: url, value: .getTransactionCount(data))
return .init(id: id, url: url, value: data)
} else {
throw PMKError.badInput
}

@ -6,7 +6,7 @@
/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md
import Foundation
import BigInt
import BigInt
import TrustKeystore
/// A struct represents EIP712 type tuple
@ -15,14 +15,6 @@ struct EIP712Type: Codable {
let type: String
}
/// A struct represents EIP712 Domain
struct EIP712Domain: Codable {
let name: String
let version: String
let chainId: Int
let verifyingContract: String
}
/// A struct represents EIP712 TypedData
struct EIP712TypedData: Codable {
let types: [String: [EIP712Type]]
@ -32,21 +24,14 @@ struct EIP712TypedData: Codable {
}
extension EIP712TypedData {
var rawStringValue: String? {
guard let value = try? JSONEncoder().encode(self), let rawValue = String(data: value, encoding: .utf8) else { return nil }
return rawValue
}
/// Type hash for the primaryType of an `EIP712TypedData`
var typeHash: Data {
let data = encodeType(primaryType: primaryType)
return Crypto.hash(data)
}
/// Sign-able hash for an `EIP712TypedData`
var signHash: Data {
let data = Data(bytes: [0x19, 0x01]) + Crypto.hash(encodeData(data: domain, type: "EIP712Domain")) + Crypto.hash(encodeData(data: message, type: primaryType))
var digest: Data {
let data = Data(bytes: [0x19, 0x01]) + hashStruct(domain, type: "EIP712Domain") + hashStruct(message, type: primaryType)
return Crypto.hash(data)
}
@ -67,6 +52,7 @@ extension EIP712TypedData {
/// Encode a type of struct
func encodeType(primaryType: String) -> Data {
NSLog(" xxx encoding primary: \(primaryType)")
var depSet = findDependencies(primaryType: primaryType)
depSet.remove(primaryType)
let sorted = [primaryType] + Array(depSet).sorted()
@ -74,6 +60,7 @@ extension EIP712TypedData {
let param = types[type]!.map { "\($0.type) \($0.name)" }.joined(separator: ",")
return "\(type)(\(param))"
}.joined()
NSLog(" xxx result: \(encoded)")
return encoded.data(using: .utf8) ?? Data()
}
@ -84,16 +71,13 @@ extension EIP712TypedData {
let encoder = ABIEncoder()
var values: [ABIValue] = []
do {
let typeHash = Crypto.hash(encodeType(primaryType: type))
let typeHashValue = try ABIValue(typeHash, type: .bytes(32))
values.append(typeHashValue)
if let valueTypes = types[type] {
try valueTypes.forEach { field in
if let _ = types[field.type],
let json = data[field.name] {
let nestEncoded = encodeData(data: json, type: field.type)
values.append(try ABIValue(Crypto.hash(nestEncoded), type: .bytes(32)))
} else if let value = makeABIValue(data: data[field.name], type: field.type) {
for field in valueTypes {
guard let value = data[field.name] else { continue }
if isStruct(field) {
let nestEncoded = hashStruct(value, type: field.type)
values.append(try ABIValue(nestEncoded, type: .bytes(32)))
} else if let value = makeABIValue(data: value, type: field.type) {
values.append(value)
}
}
@ -109,10 +93,10 @@ extension EIP712TypedData {
private func makeABIValue(data: JSON?, type: String) -> ABIValue? {
if (type == "string" || type == "bytes"), let value = data?.stringValue, let valueData = value.data(using: .utf8) {
return try? ABIValue(Crypto.hash(valueData), type: .bytes(32))
} else if type == "bool",
let value = data?.boolValue {
} else if type == "bool", let value = data?.boolValue {
return try? ABIValue(value, type: .bool)
} else if type == "address", let value = data?.stringValue, let address = Address(string: value) {
//Using `AlphaWallet.Address(uncheckedAgainstNullAddress:)` instead of `AlphaWallet.Address(string:)` because EIP712v3 test pages like to use the contract 0xb...b which fails the burn address check
} else if type == "address", let value = data?.stringValue, let address = AlphaWallet.Address(uncheckedAgainstNullAddress: value) {
return try? ABIValue(address, type: .address)
} else if type.starts(with: "uint") {
let size = parseIntSize(type: type, prefix: "uint")
@ -122,7 +106,7 @@ extension EIP712TypedData {
if let value = data?.floatValue {
return try? ABIValue(Int(value), type: .uint(bits: size))
} else if let value = data?.stringValue,
let bigInt = BigUInt(value: value) {
let bigInt = BigUInt(value: value) {
return try? ABIValue(bigInt, type: .uint(bits: size))
}
} else if type.starts(with: "int") {
@ -133,13 +117,13 @@ extension EIP712TypedData {
if let value = data?.floatValue {
return try? ABIValue(Int(value), type: .int(bits: size))
} else if let value = data?.stringValue,
let bigInt = BigInt(value: value) {
let bigInt = BigInt(value: value) {
return try? ABIValue(bigInt, type: .int(bits: size))
}
} else if type.starts(with: "bytes") {
if let length = Int(type.dropFirst("bytes".count)), let value = data?.stringValue {
if value.starts(with: "0x"),
let hex = Data(hexString: value) {
let hex = Data(hexString: value) {
return try? ABIValue(hex, type: .bytes(length))
} else {
return try? ABIValue(Data(bytes: Array(value.utf8)), type: .bytes(length))
@ -163,6 +147,18 @@ extension EIP712TypedData {
}
return size
}
private func isStruct(_ field: EIP712Type) -> Bool {
types[field.type] != nil
}
private func hashStruct(_ data: JSON, type: String) -> Data {
return Crypto.hash(typeHash(type) + encodeData(data: data, type: type))
}
private func typeHash(_ type: String) -> Data {
Crypto.hash(encodeType(primaryType: type))
}
}
private extension BigInt {

@ -10,11 +10,11 @@ import Foundation
extension WalletConnectServer {
struct Action {
enum ActionType {
case signMessage(String)
case signPersonalMessage(String)
case signTypedMessage(EIP712TypedData)
case signTypedMessageV3(EIP712TypedData)
case signTransaction(UnconfirmedTransaction)
case sendTransaction(UnconfirmedTransaction)
case sendRawTransaction(String)
@ -28,35 +28,8 @@ extension WalletConnectServer {
}
struct Callback {
enum Value {
case signTransaction(Data)
case sentTransaction(Data)
case signMessage(Data)
case signPersonalMessage(Data)
case signTypedMessage(Data)
case getTransactionCount(Data)
var object: String {
switch self {
case .signTransaction(let data):
return data.hexEncoded
case .sentTransaction(let data):
return data.hexEncoded
case .signMessage(let data):
return data.hexEncoded
case .signPersonalMessage(let data):
return data.hexEncoded
case .signTypedMessage(let data):
return data.hexEncoded
case .getTransactionCount(let data):
return data.hexEncoded
}
}
}
let id: WalletConnectRequestID
let url: WalletConnectURL
let value: Value
let value: Data
}
}

@ -6,7 +6,7 @@
//
import Foundation
import WalletConnectSwift
import WalletConnectSwift
extension WalletConnectServer {
@ -76,7 +76,7 @@ extension WalletConnectServer {
self = .sendRawTransaction(data)
case .getTransactionCount:
let data = try request.parameter(of: String.self, at: 0)
self = .getTransactionCount(data)
case .none:
self = .unknown
@ -95,7 +95,7 @@ private enum EIP712TypedDataPair: Decodable {
case invalid
}
let container = try decoder.singleValueContainer()
if let value = try? container.decode(String.self) {
self = .id(value)
} else if let value = try? container.decode(EIP712TypedData.self) {

@ -88,7 +88,7 @@ class WalletConnectServer {
}
func fulfill(_ callback: Callback, request: WalletConnectSwift.Request) throws {
let response = try Response(url: callback.url, value: callback.value.object, id: callback.id)
let response = try Response(url: callback.url, value: callback.value.hexEncoded, id: callback.id)
server.send(response)
}
@ -158,7 +158,7 @@ extension WalletConnectServer: RequestHandler {
return .value(.signTransaction(data))
case .signTypedData(_, let data):
return .value(.signTypedMessage(data))
return .value(.signTypedMessageV3(data))
case .sendTransaction(let data):
let data = UnconfirmedTransaction(transactionType: transactionType, bridge: data)

Loading…
Cancel
Save