@@ -15,6 +15,9 @@ private let uriPattern = "(([^:?#\\s]+):)?(([^?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*
1515
1616/// Sign-In with Ethereum protocol and parser implementation.
1717///
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+ ///
1821/// EIP-4361:
1922/// - https://eips.ethereum.org/EIPS/eip-4361
2023/// - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4361.md
@@ -60,22 +63,64 @@ public final class EIP4361 {
6063 case resources
6164 }
6265
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) ))? "
79124
80125 private static var _eip4361OptionalPattern : String ?
81126 private static var eip4361OptionalPattern : String {
@@ -95,7 +140,7 @@ public final class EIP4361 {
95140
96141 let patternParts : [ String ] = [ " ^ \( domain) " ,
97142 " ( \( address) )? " ,
98- " \( statementParagraph ) " ,
143+ " ((?< \( EIP4361Field . statement . rawValue ) > \( statement ) ) \\ n)? " ,
99144 " ( \( uri) )? " ,
100145 " ( \( version) )? " ,
101146 " ( \( chainId) )? " ,
@@ -113,12 +158,7 @@ public final class EIP4361 {
113158
114159 public static func validate( _ message: String ) -> EIP4361ValidationResponse {
115160 // 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)
122162 // swiftlint:enable force_try
123163 var capturedFields : [ EIP4361Field : String ] = [ : ]
124164 for (key, value) in eip4361Regex. captureGroups ( string: message) {
@@ -129,7 +169,7 @@ public final class EIP4361 {
129169 // swiftlint:enable force_unwrapping
130170 }
131171 return EIP4361ValidationResponse ( isEIP4361: true ,
132- eip4361: EIP4361 ( message) ,
172+ eip4361: EIP4361 ( message) ,
133173 capturedFields: capturedFields)
134174 }
135175
@@ -167,6 +207,7 @@ public final class EIP4361 {
167207 let dateFormatter = ISO8601DateFormatter ( )
168208 dateFormatter. formatOptions = [ . withInternetDateTime, . withFractionalSeconds]
169209 guard let domain = groups [ " domain " ] ,
210+ !domain. isEmpty,
170211 let rawAddress = groups [ " address " ] ,
171212 let address = EthereumAddress ( rawAddress) ,
172213 let rawUri = groups [ " uri " ] ,
0 commit comments