@@ -15,6 +15,9 @@ private let uriPattern = "(([^:?#\\s]+):)?(([^?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*
15
15
16
16
/// Sign-In with Ethereum protocol and parser implementation.
17
17
///
18
+ /// _Regular expressions were generated using ABNF grammar from https://github.com/spruceid/siwe/blob/main/packages/siwe-parser/lib/abnf.ts#L5
19
+ /// and tool https://pypi.org/project/abnf-to-regexp/ that outputs Python supported regular expressions._
20
+ ///
18
21
/// EIP-4361:
19
22
/// - https://eips.ethereum.org/EIPS/eip-4361
20
23
/// - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4361.md
@@ -60,22 +63,64 @@ public final class EIP4361 {
60
63
case resources
61
64
}
62
65
63
- private static let domain = " (?< \( EIP4361Field . domain. rawValue) >([^?#]*)) wants you to sign in with your Ethereum account: "
64
- private static let address = " \\ n(?< \( EIP4361Field . address. rawValue) >0x[a-zA-Z0-9]{40}) \\ n \\ n "
65
- private static let statementParagraph = " ((?< \( EIP4361Field . statement. rawValue) >[^ \\ n]+) \\ n)? "
66
- private static let uri = " \\ nURI: (?< \( EIP4361Field . uri. rawValue) >( \( uriPattern) )?) "
67
- private static let version = " \\ nVersion: (?< \( EIP4361Field . version. rawValue) >[0-9]+) "
68
- private static let chainId = " \\ nChain ID: (?< \( EIP4361Field . chainId. rawValue) >[0-9a-fA-F]+) "
69
- private static let nonce = " \\ nNonce: (?< \( EIP4361Field . nonce. rawValue) >[a-zA-Z0-9]{8,}) "
70
- private static let issuedAt = " \\ nIssued At: (?< \( EIP4361Field . issuedAt. rawValue) >( \( datetimePattern) )) "
71
- private static let expirationTime = " ( \\ nExpiration Time: (?< \( EIP4361Field . expirationTime. rawValue) >( \( datetimePattern) )))? "
72
- private static let notBefore = " ( \\ nNot Before: (?< \( EIP4361Field . notBefore. rawValue) >( \( datetimePattern) )))? "
73
- private static let requestId = " ( \\ nRequest ID: (?< \( EIP4361Field . requestId. rawValue) >[-._~!$&'()*+,;=:@%a-zA-Z0-9]*))? "
74
- private static let resourcesParagraph = " ( \\ nResources:(?< \( EIP4361Field . resources. rawValue) >( \\ n- ( \( uriPattern) )?)+))? "
75
-
76
- private static var eip4361Pattern : String {
77
- " ^ \( domain) \( address) \( statementParagraph) \( uri) \( version) \( chainId) \( nonce) \( issuedAt) \( expirationTime) \( notBefore) \( requestId) \( resourcesParagraph) $ "
78
- }
66
+ private static let unreserved = " [a-zA-Z0-9 \\ -._~] "
67
+ private static let pctEncoded = " %[0-9A-Fa-f][0-9A-Fa-f] "
68
+ private static let subDelims = " [!$&'()*+,;=] "
69
+ private static let userinfo = " ( \( unreserved) | \( pctEncoded) | \( subDelims) |:)* "
70
+ private static let h16 = " [0-9A-Fa-f]{1,4} "
71
+ private static let decOctet = " ([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]) "
72
+ private static let ipv4address = " \( decOctet) \\ . \( decOctet) \\ . \( decOctet) \\ . \( decOctet) "
73
+ private static let ls32 = " ( \( h16) : \( h16) | \( ipv4address) ) "
74
+ private static let ipv6address = " (( \( h16) :){6} \( ls32) |::( \( h16) :){5} \( ls32) |( \( h16) )?::( \( h16) :){4} \( ls32) |(( \( h16) :)? \( h16) )?::( \( h16) :){3} \( ls32) |(( \( h16) :){2} \( h16) )?::( \( h16) :){2} \( ls32) |(( \( h16) :){3} \( h16) )?:: \( h16) : \( ls32) |(( \( h16) :){4} \( h16) )?:: \( ls32) |(( \( h16) :){5} \( h16) )?:: \( h16) |(( \( h16) :){6} \( h16) )?::) "
75
+ private static let ipvfuture = " [vV][0-9A-Fa-f]+ \\ .( \( unreserved) | \( subDelims) |:)+ "
76
+ private static let ipLiteral = " \\ [( \( ipv6address) | \( ipvfuture) ) \\ ] "
77
+ private static let regName = " ( \( unreserved) | \( pctEncoded) | \( subDelims) )* "
78
+ private static let host = " ( \( ipLiteral) | \( ipv4address) | \( regName) ) "
79
+ private static let port = " [0-9]* "
80
+ private static let authority = " ( \( userinfo) @)? \( host) (: \( port) )? "
81
+ private static let dateFullyear = " [0-9]{4} "
82
+ private static let dateMday = " [0-9]{2} "
83
+ private static let dateMonth = " [0-9]{2} "
84
+ private static let fullDate = " \( dateFullyear) - \( dateMonth) - \( dateMday) "
85
+ private static let timeHour = " [0-9]{2} "
86
+ private static let timeMinute = " [0-9]{2} "
87
+ private static let timeSecond = " [0-9]{2} "
88
+ private static let timeSecfrac = " \\ .[0-9]+ "
89
+ private static let partialTime = " \( timeHour) : \( timeMinute) : \( timeSecond) ( \( timeSecfrac) )? "
90
+ private static let timeNumoffset = " [+ \\ -] \( timeHour) : \( timeMinute) "
91
+ private static let timeOffset = " ([zZ]| \( timeNumoffset) ) "
92
+ private static let fullTime = " \( partialTime) \( timeOffset) "
93
+ private static let dateTime = " \( fullDate) [tT] \( fullTime) "
94
+ private static let pchar = " ( \( unreserved) | \( pctEncoded) | \( subDelims) |[:@]) "
95
+ private static let fragment = " ( \( pchar) |[/?])* "
96
+ private static let genDelims = " [:/?# \\ [ \\ ]@] "
97
+ private static let segment = " ( \( pchar) )* "
98
+ private static let pathAbempty = " (/ \( segment) )* "
99
+ private static let segmentNz = " ( \( pchar) )+ "
100
+ private static let pathAbsolute = " /( \( segmentNz) (/ \( segment) )*)? "
101
+ private static let pathRootless = " \( segmentNz) (/ \( segment) )* "
102
+ private static let pathEmpty = " ( \( pchar) ){0} "
103
+ private static let hierPart = " (// \( authority) \( pathAbempty) | \( pathAbsolute) | \( pathRootless) | \( pathEmpty) ) "
104
+ private static let query = " ( \( pchar) |[/?])* "
105
+ private static let reserved = " ( \( genDelims) | \( subDelims) ) "
106
+ private static let scheme = " [a-zA-Z][a-zA-Z0-9+ \\ -.]* "
107
+ private static let resource = " - \( uri) "
108
+
109
+ // MARK: The final regular expression parts
110
+ private static let domain = authority
111
+ private static let address = " 0x[0-9A-Fa-f]{40} "
112
+ private static let statement = " ( \( reserved) | \( unreserved) | )+ "
113
+ private static let uri = " \( scheme) : \( hierPart) ( \\ ? \( query) )?( \\ # \( fragment) )? "
114
+ private static let version = " [0-9]+ "
115
+ private static let chainId = " [0-9]+ "
116
+ private static let nonce = " [a-zA-Z0-9]{8,} "
117
+ private static let issuedAt = dateTime
118
+ private static let expirationTime = dateTime
119
+ private static let notBefore = dateTime
120
+ private static let requestId = " ( \( pchar) )* "
121
+ private static let resources = " ( \\ n \( resource) )* "
122
+
123
+ private static let eip4361Pattern = " (?< \( EIP4361Field . domain. rawValue) > \( domain) ) wants you to sign in with your Ethereum account: \\ n(?< \( EIP4361Field . address. rawValue) > \( address) ) \\ n \\ n((?< \( EIP4361Field . statement. rawValue) > \( statement) ) \\ n)? \\ nURI: (?< \( EIP4361Field . uri. rawValue) > \( uri) ) \\ nVersion: (?< \( EIP4361Field . version. rawValue) > \( version) ) \\ nChain ID: (?< \( EIP4361Field . chainId. rawValue) > \( chainId) ) \\ nNonce: (?< \( EIP4361Field . nonce. rawValue) > \( nonce) ) \\ nIssued At: (?< \( EIP4361Field . issuedAt. rawValue) > \( issuedAt) )( \\ nExpiration Time: (?< \( EIP4361Field . expirationTime. rawValue) > \( expirationTime) ))?( \\ nNot Before: (?< \( EIP4361Field . notBefore. rawValue) > \( notBefore) ))?( \\ nRequest ID: (?< \( EIP4361Field . requestId. rawValue) > \( requestId) ))?( \\ nResources:(?< \( EIP4361Field . resources. rawValue) > \( resources) ))? "
79
124
80
125
private static var _eip4361OptionalPattern : String ?
81
126
private static var eip4361OptionalPattern : String {
@@ -95,7 +140,7 @@ public final class EIP4361 {
95
140
96
141
let patternParts : [ String ] = [ " ^ \( domain) " ,
97
142
" ( \( address) )? " ,
98
- " \( statementParagraph ) " ,
143
+ " ((?< \( EIP4361Field . statement . rawValue ) > \( statement ) ) \\ n)? " ,
99
144
" ( \( uri) )? " ,
100
145
" ( \( version) )? " ,
101
146
" ( \( chainId) )? " ,
@@ -113,12 +158,7 @@ public final class EIP4361 {
113
158
114
159
public static func validate( _ message: String ) -> EIP4361ValidationResponse {
115
160
// swiftlint:disable force_try
116
- let siweConstantMessageRegex = try ! NSRegularExpression ( pattern: " ^ \( domain) \\ n " )
117
- guard siweConstantMessageRegex. firstMatch ( in: message, range: message. fullNSRange) != nil else {
118
- return EIP4361ValidationResponse ( isEIP4361: false , eip4361: nil , capturedFields: [ : ] )
119
- }
120
-
121
- let eip4361Regex = try ! NSRegularExpression ( pattern: eip4361OptionalPattern)
161
+ let eip4361Regex = try ! NSRegularExpression ( pattern: EIP4361 . eip4361OptionalPattern)
122
162
// swiftlint:enable force_try
123
163
var capturedFields : [ EIP4361Field : String ] = [ : ]
124
164
for (key, value) in eip4361Regex. captureGroups ( string: message) {
@@ -129,7 +169,7 @@ public final class EIP4361 {
129
169
// swiftlint:enable force_unwrapping
130
170
}
131
171
return EIP4361ValidationResponse ( isEIP4361: true ,
132
- eip4361: EIP4361 ( message) ,
172
+ eip4361: EIP4361 ( message) ,
133
173
capturedFields: capturedFields)
134
174
}
135
175
@@ -167,6 +207,7 @@ public final class EIP4361 {
167
207
let dateFormatter = ISO8601DateFormatter ( )
168
208
dateFormatter. formatOptions = [ . withInternetDateTime, . withFractionalSeconds]
169
209
guard let domain = groups [ " domain " ] ,
210
+ !domain. isEmpty,
170
211
let rawAddress = groups [ " address " ] ,
171
212
let address = EthereumAddress ( rawAddress) ,
172
213
let rawUri = groups [ " uri " ] ,
0 commit comments