@@ -16,6 +16,12 @@ public class EIP712 {
1616 public typealias Bytes = Data
1717}
1818
19+ // FIXME: this type is wrong - The minimum number of optional fields is 5, and those are
20+ // string name the user readable name of signing domain, i.e. the name of the DApp or the protocol.
21+ // string version the current major version of the signing domain. Signatures from different versions are not compatible.
22+ // uint256 chainId the EIP-155 chain id. The user-agent should refuse signing if it does not match the currently active chain.
23+ // address verifyingContract the address of the contract that will verify the signature. The user-agent may do contract specific phishing prevention.
24+ // bytes32 salt an disambiguating salt for the protocol. This can be used as a domain separator of last resort.
1925public struct EIP712Domain : EIP712Hashable {
2026 public let chainId : EIP712 . UInt256 ?
2127 public let verifyingContract : EIP712 . Address
@@ -54,7 +60,10 @@ public extension EIP712Hashable {
5460 result = ABIEncoder . encodeSingleType ( type: . uint( bits: 256 ) , value: field) !
5561 case is EIP712 . Address :
5662 result = ABIEncoder . encodeSingleType ( type: . address, value: field) !
63+ case let boolean as Bool :
64+ result = ABIEncoder . encodeSingleType ( type: . uint( bits: 8 ) , value: boolean ? 1 : 0 ) !
5765 case let hashable as EIP712Hashable :
66+ // TODO: should it be hashed here?
5867 result = try hashable. hash ( )
5968 default :
6069 /// Cast to `AnyObject` is required. Otherwise, `nil` value will fail this condition.
@@ -64,16 +73,77 @@ public extension EIP712Hashable {
6473 preconditionFailure ( " Not solidity type " )
6574 }
6675 }
67- guard result. count == 32 else { preconditionFailure ( " ABI encode error " ) }
76+ guard result. count % 32 == 0 else { preconditionFailure ( " ABI encode error " ) }
6877 parameters. append ( result)
6978 }
7079 return Data ( parameters. flatMap { $0. bytes } ) . sha3 ( . keccak256)
7180 }
7281}
7382
74- public func eip712encode( domainSeparator: EIP712Hashable , message: EIP712Hashable ) throws -> Data {
75- let data = try Data ( [ UInt8 ( 0x19 ) , UInt8 ( 0x01 ) ] ) + domainSeparator. hash ( ) + message. hash ( )
76- return data. sha3 ( . keccak256)
83+ public func eip712hash( domainSeparator: EIP712Hashable , message: EIP712Hashable ) throws -> Data {
84+ try eip712hash ( domainSeparatorHash: domainSeparator. hash ( ) , messageHash: message. hash ( ) )
85+ }
86+
87+ public func eip712hash( _ eip712TypedData: EIP712TypedData ) throws -> Data {
88+ guard let chainId = eip712TypedData. domain [ " chainId " ] as? Int64 ,
89+ let verifyingContract = eip712TypedData. domain [ " verifyingContract " ] as? String ,
90+ let verifyingContractAddress = EIP712 . Address ( verifyingContract)
91+ else {
92+ throw Web3Error . inputError ( desc: " Failed to parse chainId or verifyingContract address. Domain object is \( eip712TypedData. domain) . " )
93+ }
94+
95+ let domainHash = try EIP712Domain ( chainId: EIP712 . UInt256 ( chainId) , verifyingContract: verifyingContractAddress) . hash ( )
96+ guard let primaryTypeData = eip712TypedData. types [ eip712TypedData. primaryType] else {
97+ throw Web3Error . inputError ( desc: " EIP712 hashing error. Given primary type name is not present amongst types. primaryType - \( eip712TypedData. primaryType) ; available types - \( eip712TypedData. types. values) " )
98+ }
99+
100+ let messageHash = try hashEip712Message ( eip712TypedData,
101+ eip712TypedData. message,
102+ messageTypeData: primaryTypeData)
103+ return eip712hash ( domainSeparatorHash: domainHash, messageHash: messageHash)
104+ }
105+
106+ func hashEip712Message( _ typedData: EIP712TypedData , _ message: [ String : AnyObject ] , messageTypeData: [ EIP712TypeProperty ] ) throws -> Data {
107+ var messageData : [ Data ] = [ ]
108+ for field in messageTypeData {
109+ guard let fieldValue = message [ field. name] else {
110+ throw Web3Error . inputError ( desc: " EIP712 message doesn't have field with name \( field. name) . " )
111+ }
112+
113+ if let customType = typedData. types [ field. type] {
114+ guard let objectAttribute = fieldValue as? [ String : AnyObject ] else {
115+ throw Web3Error . processingError ( desc: " Failed to hash EIP712 message. A property from 'message' field with custom type cannot be represented as object and thus encoded & hashed. Property name \( field. name) ; value \( String ( describing: message [ field. name] ) ) . " )
116+ }
117+ try messageData. append ( hashEip712Message ( typedData, objectAttribute, messageTypeData: customType) )
118+ } else {
119+ let type = try ABITypeParser . parseTypeString ( field. type)
120+ var data : Data ?
121+ switch type {
122+ case . dynamicBytes, . bytes:
123+ if let bytes = fieldValue as? Data {
124+ data = bytes. sha3 ( . keccak256)
125+ }
126+ case . string:
127+ if let string = fieldValue as? String {
128+ data = Data ( string. bytes) . sha3 ( . keccak256)
129+ }
130+ default :
131+ data = ABIEncoder . encodeSingleType ( type: type, value: fieldValue)
132+ }
133+
134+ if let data = data {
135+ messageData. append ( data)
136+ } else {
137+ throw Web3Error . processingError ( desc: " Failed to encode property of EIP712 message. Property name \( field. name) ; value \( String ( describing: message [ field. name] ) ) " )
138+ }
139+ }
140+ }
141+
142+ return Data ( messageData. flatMap { $0. bytes } ) . sha3 ( . keccak256)
143+ }
144+
145+ public func eip712hash( domainSeparatorHash: Data , messageHash: Data ) -> Data {
146+ ( Data ( [ UInt8 ( 0x19 ) , UInt8 ( 0x01 ) ] ) + domainSeparatorHash + messageHash) . sha3 ( . keccak256)
77147}
78148
79149// MARK: - Additional private and public extensions with support members
0 commit comments