|
@@ -59,7 +59,7 @@ An Extended PASA is defined by the below EBNF grammar:
|
|
PascalAsciiChar = (" " | "!" | EscapeChar, """ | "#" | "$" | "%" | "&" | "'" | EscapeChar, "(" | EscapeChar, ")" | "*" | "+" | "," | "-" | "." | "/" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | EscapeChar, ":" | ";" | EscapeChar, "<" | "=" | EscapeChar, ">" | "?" | "@" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | EscapeChar, "[" | EscapeChar, "\" | EscapeChar, "]" | "^" | "_" | "`" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | EscapeChar, "{" | "|" | EscapeChar, "}" | "~") ;
|
|
PascalAsciiChar = (" " | "!" | EscapeChar, """ | "#" | "$" | "%" | "&" | "'" | EscapeChar, "(" | EscapeChar, ")" | "*" | "+" | "," | "-" | "." | "/" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | EscapeChar, ":" | ";" | EscapeChar, "<" | "=" | EscapeChar, ">" | "?" | "@" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | EscapeChar, "[" | EscapeChar, "\" | EscapeChar, "]" | "^" | "_" | "`" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | EscapeChar, "{" | "|" | EscapeChar, "}" | "~") ;
|
|
Pascal64String = Pascal64StartChar, { Pascal64Char } ;
|
|
Pascal64String = Pascal64StartChar, { Pascal64Char } ;
|
|
Pascal64Char = (Digit | Pascal64StartChar)
|
|
Pascal64Char = (Digit | Pascal64StartChar)
|
|
- Pascal64StartChar = ( "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "!" | "@" | "#" | "$" | "%" | "^" | "&" | "*" | EscapeChar, "(" | EscapeChar, ")" | "-" | "+" | EscapeChar, "{" | EscapeChar, "}" | EscapeChar, "[" | EscapeChar, "]" | "_" | EscapeChar, ":" | "`" | "|" | EscapeChar, "<" | EscapeChar, ">" | "," | "." | "?" | "/" | "~" ) ;
|
|
|
|
|
|
+ Pascal64StartChar = ( "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "!" | "@" | "#" | "$" | "%" | "^" | "&" | "*" | EscapeChar, "(" | EscapeChar, ")" | "-" | "+" | EscapeChar, "{" | EscapeChar, "}" | EscapeChar, "[" | EscapeChar, "]" | "_" | EscapeChar, ":" | EscapeChar, """ | "`" | "|" | EscapeChar, "<" | EscapeChar, ">" | "," | "." | "?" | "/" | "~" ) ;
|
|
HexString = HexByte { HexByte } ;
|
|
HexString = HexByte { HexByte } ;
|
|
HexByte = HexNibble, HexNibble ;
|
|
HexByte = HexNibble, HexNibble ;
|
|
HexNibble = ( Digit | "a" | "b" | "c" | "d" | "e" | "f" ) ; (* no uppercase hex allowed *)
|
|
HexNibble = ( Digit | "a" | "b" | "c" | "d" | "e" | "f" ) ; (* no uppercase hex allowed *)
|
|
@@ -71,15 +71,17 @@ An Extended PASA is defined by the below EBNF grammar:
|
|
Digit = ( "0" | NaturalDigit ) ;
|
|
Digit = ( "0" | NaturalDigit ) ;
|
|
NaturalDigit = ( "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ) ;
|
|
NaturalDigit = ( "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ) ;
|
|
EscapeChar = "\" ;
|
|
EscapeChar = "\" ;
|
|
|
|
+```
|
|
|
|
|
|
**NOTES**:
|
|
**NOTES**:
|
|
* Text payload and passwords are restricted to ANSI charset subset range 32..126
|
|
* Text payload and passwords are restricted to ANSI charset subset range 32..126
|
|
- * The following characters are escaped in Pascal64 and PascalAscii strings): **:**, **\\**, **"**, **[**, **]**, **(**, **), **<**, **>**,**{**, **}**
|
|
|
|
|
|
+ * The following characters are escaped in **Pascal64** encoding: **(** **(** **)** **{** **}** **[** **]** **:** **"** **<** **>**
|
|
|
|
+ * The following characters are escaped in **PascalAscii** encoding: **"** **(** **)** **:** **<** **>** **[** **\\** **]** **{** **}**
|
|
* Escape character is always: **\\**
|
|
* Escape character is always: **\\**
|
|
|
|
|
|
The above rules can be interpreted as follows:
|
|
The above rules can be interpreted as follows:
|
|
|
|
|
|
-```
|
|
|
|
|
|
+
|
|
| Rule | Interpretation |
|
|
| Rule | Interpretation |
|
|
| -----------------: | :------------------------------------------------------------------------------------------------------------ |
|
|
| -----------------: | :------------------------------------------------------------------------------------------------------------ |
|
|
| EPASA | This is a layer-2 address, fully backwards compatible as Layer-1 address |
|
|
| EPASA | This is a layer-2 address, fully backwards compatible as Layer-1 address |
|
|
@@ -260,6 +262,10 @@ The values are interpreted as follows:
|
|
|
|
|
|
## E-PASA Examples
|
|
## E-PASA Examples
|
|
|
|
|
|
|
|
+The below cases are only example E-PASA without valid checksums.
|
|
|
|
+
|
|
|
|
+**TODO**: replace with real E-Pasa after implementation QA.
|
|
|
|
+
|
|
### Base Cases
|
|
### Base Cases
|
|
|
|
|
|
<table>
|
|
<table>
|
|
@@ -372,242 +378,242 @@ For Layer-2 applications the ability for a receiver to auto-decode the E-PASA vi
|
|
|
|
|
|
The following regex parses an e-pasa:
|
|
The following regex parses an e-pasa:
|
|
```
|
|
```
|
|
-((?<AccountNumber>(0|[1-9]\d+))(?:(?<ChecksumDelim>-)(?<Checksum>\d{2}))?|(?<AccountName>(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|!|@|#|\$|%|\^|&|\*|\\\(|\\\)|-|\+|\\\{|\\\}|\\\[|\\]|_|\\:|`|\||\\<|\\>|,|\.|\?|/|~)(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|0|1|2|3|4|5|6|7|8|9|!|@|#|\$|%|\^|&|\*|\\\(|\\\)|-|\+|\\\{|\\\}|\\\[|\\]|_|\\:|`|\||\\<|\\>|,|\.|\?|/|~){2,63}))(?:(?<PayloadStartChar>[\[\(<\{])(?<PayloadContent>"( |!|\\"|#|\$|%|&|'|\\\(|\\\)|\*|\+|,|-|\.|/|0|1|2|3|4|5|6|7|8|9|\\:|;|\\<|=|\\>|\?|@|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|\\\[|\\\\|\\]|\^|_|`|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|\\\{|\||\\\}|~)+"|0x(?:[0-9a-f]{2})+|[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)?(?:(?<PayloadPasswordDelim>:){1}(?<PayloadPassword>( |!|\\"|#|\$|%|&|'|\\\(|\\\)|\*|\+|,|-|\.|/|0|1|2|3|4|5|6|7|8|9|\\:|;|\\<|=|\\>|\?|@|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|\\\[|\\\\|\\]|\^|_|`|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|\\\{|\||\\\}|~)+)?)?(?<PayloadEndChar>[]\)>\}]))?(?:(?<ExtendedChecksumDelim>:)(?<ExtendedChecksum>[0-9a-f]{2}[0-9a-f]{2}))?
|
|
|
|
|
|
+((?<AccountNumber>(0|[1-9]\d+))(?:(?<ChecksumDelim>-)(?<Checksum>\d{2}))?|(?<AccountName>(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|!|@|#|\$|%|\^|&|\*|\\\(|\\\)|-|\+|\\\{|\\\}|\\\[|\\]|_|\\:|\\"|`|\||\\<|\\>|,|\.|\?|/|~)(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|0|1|2|3|4|5|6|7|8|9|!|@|#|\$|%|\^|&|\*|\\\(|\\\)|-|\+|\\\{|\\\}|\\\[|\\]|_|\\:|\\"|`|\||\\<|\\>|,|\.|\?|/|~){2,63}))(?:(?<PayloadStartChar>[\[\(<\{])(?<PayloadContent>"( |!|\\"|#|\$|%|&|'|\\\(|\\\)|\*|\+|,|-|\.|/|0|1|2|3|4|5|6|7|8|9|\\:|;|\\<|=|\\>|\?|@|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|\\\[|\\\\|\\]|\^|_|`|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|\\\{|\||\\\}|~)+"|0x(?:[0-9a-f]{2})+|[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)?(?:(?<PayloadPasswordDelim>:){1}(?<PayloadPassword>( |!|\\"|#|\$|%|&|'|\\\(|\\\)|\*|\+|,|-|\.|/|0|1|2|3|4|5|6|7|8|9|\\:|;|\\<|=|\\>|\?|@|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|\\\[|\\\\|\\]|\^|_|`|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|\\\{|\||\\\}|~)+)?)?(?<PayloadEndChar>[]\)>\}]))?(?:(?<ExtendedChecksumDelim>:)(?<ExtendedChecksum>[0-9a-f]{2}[0-9a-f]{2}))?
|
|
```
|
|
```
|
|
|
|
|
|
After matching with the above regex, the named groups need to be extracted and validated as the below snippet shows
|
|
After matching with the above regex, the named groups need to be extracted and validated as the below snippet shows
|
|
|
|
|
|
```csharp
|
|
```csharp
|
|
- public override bool TryParse(string epasaText, out EPasa epasa, out EPasaErrorCode errorCode) {
|
|
|
|
- errorCode = EPasaErrorCode.Success;
|
|
|
|
- epasa = new EPasa();
|
|
|
|
-
|
|
|
|
- if (string.IsNullOrEmpty(epasaText)) {
|
|
|
|
- errorCode = EPasaErrorCode.BadFormat;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- var match = _epasaRegex.Match(epasaText);
|
|
|
|
- var checksumDelim = match.Groups["ChecksumDelim"].Success ? match.Groups["ChecksumDelim"].Value : null;
|
|
|
|
- var accountNumber = match.Groups["AccountNumber"].Success ? match.Groups["AccountNumber"].Value : null;
|
|
|
|
- var accountChecksum = match.Groups["Checksum"].Success ? match.Groups["Checksum"].Value : null;
|
|
|
|
- var accountName = match.Groups["AccountName"].Success ? match.Groups["AccountName"].Value : null;
|
|
|
|
- var payloadStartChar = match.Groups["PayloadStartChar"].Success ? match.Groups["PayloadStartChar"].Value : null;
|
|
|
|
- var payloadEndChar = match.Groups["PayloadEndChar"].Success ? match.Groups["PayloadEndChar"].Value : null;
|
|
|
|
- var payloadContent = match.Groups["PayloadContent"].Success ? match.Groups["PayloadContent"].Value : null;
|
|
|
|
- var payloadPasswordDelim = match.Groups["PayloadPasswordDelim"].Success ? match.Groups["PayloadPasswordDelim"].Value : null;
|
|
|
|
- var payloadPassword = match.Groups["PayloadPassword"].Success ? match.Groups["PayloadPassword"].Value : null;
|
|
|
|
- var extendedChecksumDelim = match.Groups["ExtendedChecksumDelim"].Success ? match.Groups["ExtendedChecksumDelim"].Value : null;
|
|
|
|
- var extendedChecksum = match.Groups["ExtendedChecksum"].Success ? match.Groups["ExtendedChecksum"].Value : null;
|
|
|
|
-
|
|
|
|
- // Check parsed completely
|
|
|
|
- if (epasaText != match.Value) {
|
|
|
|
- errorCode = EPasaErrorCode.BadFormat;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (accountName != null) {
|
|
|
|
- // Account Name
|
|
|
|
- if (string.IsNullOrEmpty(accountName)) {
|
|
|
|
- errorCode = EPasaErrorCode.BadFormat;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- epasa.PayloadType = epasa.PayloadType | PayloadType.AddressedByName;
|
|
|
|
- epasa.AccountName = accountName;
|
|
|
|
- epasa.Account = epasa.AccountChecksum = null;
|
|
|
|
- } else {
|
|
|
|
- // Account Number
|
|
|
|
- if (!uint.TryParse(accountNumber, out var accNo)) {
|
|
|
|
- errorCode = EPasaErrorCode.InvalidAccountNumber;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- epasa.Account = accNo;
|
|
|
|
- var actualAccountChecksum = AccountHelper.CalculateAccountChecksum(accNo);
|
|
|
|
-
|
|
|
|
- if (checksumDelim != null) {
|
|
|
|
- // validate account checksum
|
|
|
|
- if (!uint.TryParse(accountChecksum, out var accChecksum)) {
|
|
|
|
- errorCode = EPasaErrorCode.AccountChecksumInvalid;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- if (accChecksum != actualAccountChecksum) {
|
|
|
|
- errorCode = EPasaErrorCode.BadChecksum;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- epasa.AccountChecksum = actualAccountChecksum;
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Encryption type
|
|
|
|
- switch (payloadStartChar) {
|
|
|
|
- case null:
|
|
|
|
- break;
|
|
|
|
- case "[":
|
|
|
|
- if (payloadEndChar != "]") {
|
|
|
|
- errorCode = EPasaErrorCode.MismatchedPayloadEncoding;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- epasa.PayloadType = epasa.PayloadType | PayloadType.Public;
|
|
|
|
- break;
|
|
|
|
- case "(":
|
|
|
|
- if (payloadEndChar != ")") {
|
|
|
|
- errorCode = EPasaErrorCode.MismatchedPayloadEncoding;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- epasa.PayloadType = epasa.PayloadType | PayloadType.RecipientKeyEncrypted;
|
|
|
|
- break;
|
|
|
|
- case "<":
|
|
|
|
- if (payloadEndChar != ">") {
|
|
|
|
- errorCode = EPasaErrorCode.MismatchedPayloadEncoding;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- epasa.PayloadType = epasa.PayloadType | PayloadType.SenderKeyEncrypted;
|
|
|
|
- break;
|
|
|
|
- case "{":
|
|
|
|
- if (payloadEndChar != "}") {
|
|
|
|
- errorCode = EPasaErrorCode.MismatchedPayloadEncoding;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- epasa.PayloadType = epasa.PayloadType | PayloadType.PasswordEncrypted;
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
- throw new NotSupportedException($"Unrecognized start character '{payloadStartChar}'");
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Password
|
|
|
|
- if (epasa.PayloadType.HasFlag(PayloadType.PasswordEncrypted)) {
|
|
|
|
- if (payloadPasswordDelim == null) {
|
|
|
|
- errorCode = EPasaErrorCode.MissingPassword;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- epasa.Password = payloadPassword ?? "";
|
|
|
|
- } else if (payloadPasswordDelim != null) {
|
|
|
|
- errorCode = EPasaErrorCode.UnusedPassword;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Payload
|
|
|
|
- if (payloadStartChar != null) {
|
|
|
|
- if (payloadContent == null) {
|
|
|
|
- epasa.Payload = string.Empty;
|
|
|
|
- } else if (payloadContent.StartsWith("\"")) {
|
|
|
|
- epasa.PayloadType = epasa.PayloadType | PayloadType.AsciiFormatted;
|
|
|
|
- epasa.Payload = payloadContent.Trim('"');
|
|
|
|
- } else if (payloadContent.StartsWith("0x")) {
|
|
|
|
- epasa.PayloadType = epasa.PayloadType | PayloadType.HexFormatted;
|
|
|
|
- epasa.Payload = payloadContent.Substring(2);
|
|
|
|
- } else {
|
|
|
|
- epasa.PayloadType = epasa.PayloadType | PayloadType.Base58Formatted;
|
|
|
|
- epasa.Payload = payloadContent;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Payload Lengths
|
|
|
|
- if (!EPasaHelper.IsValidPayloadLength(epasa.PayloadType, epasa.Payload)) {
|
|
|
|
- errorCode = EPasaErrorCode.PayloadTooLarge;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Extended Checksum
|
|
|
|
- var actualChecksum = EPasaHelper.ComputeExtendedChecksum(epasa.ToString(true));
|
|
|
|
- if (extendedChecksumDelim != null) {
|
|
|
|
- if (extendedChecksum != actualChecksum) {
|
|
|
|
- errorCode = EPasaErrorCode.BadExtendedChecksum;
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- epasa.ExtendedChecksum = actualChecksum;
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
|
|
+public override bool TryParse(string epasaText, out EPasa epasa, out EPasaErrorCode errorCode) {
|
|
|
|
+ errorCode = EPasaErrorCode.Success;
|
|
|
|
+ epasa = new EPasa();
|
|
|
|
+
|
|
|
|
+ if (string.IsNullOrEmpty(epasaText)) {
|
|
|
|
+ errorCode = EPasaErrorCode.BadFormat;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var match = _epasaRegex.Match(epasaText);
|
|
|
|
+ var checksumDelim = match.Groups["ChecksumDelim"].Success ? match.Groups["ChecksumDelim"].Value : null;
|
|
|
|
+ var accountNumber = match.Groups["AccountNumber"].Success ? match.Groups["AccountNumber"].Value : null;
|
|
|
|
+ var accountChecksum = match.Groups["Checksum"].Success ? match.Groups["Checksum"].Value : null;
|
|
|
|
+ var accountName = match.Groups["AccountName"].Success ? match.Groups["AccountName"].Value : null;
|
|
|
|
+ var payloadStartChar = match.Groups["PayloadStartChar"].Success ? match.Groups["PayloadStartChar"].Value : null;
|
|
|
|
+ var payloadEndChar = match.Groups["PayloadEndChar"].Success ? match.Groups["PayloadEndChar"].Value : null;
|
|
|
|
+ var payloadContent = match.Groups["PayloadContent"].Success ? match.Groups["PayloadContent"].Value : null;
|
|
|
|
+ var payloadPasswordDelim = match.Groups["PayloadPasswordDelim"].Success ? match.Groups["PayloadPasswordDelim"].Value : null;
|
|
|
|
+ var payloadPassword = match.Groups["PayloadPassword"].Success ? match.Groups["PayloadPassword"].Value : null;
|
|
|
|
+ var extendedChecksumDelim = match.Groups["ExtendedChecksumDelim"].Success ? match.Groups["ExtendedChecksumDelim"].Value : null;
|
|
|
|
+ var extendedChecksum = match.Groups["ExtendedChecksum"].Success ? match.Groups["ExtendedChecksum"].Value : null;
|
|
|
|
+
|
|
|
|
+ // Check parsed completely
|
|
|
|
+ if (epasaText != match.Value) {
|
|
|
|
+ errorCode = EPasaErrorCode.BadFormat;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (accountName != null) {
|
|
|
|
+ // Account Name
|
|
|
|
+ if (string.IsNullOrEmpty(accountName)) {
|
|
|
|
+ errorCode = EPasaErrorCode.BadFormat;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ epasa.PayloadType = epasa.PayloadType | PayloadType.AddressedByName;
|
|
|
|
+ epasa.AccountName = Pascal64Encoding.Unescape(accountName);
|
|
|
|
+ epasa.Account = epasa.AccountChecksum = null;
|
|
|
|
+ } else {
|
|
|
|
+ // Account Number
|
|
|
|
+ if (!uint.TryParse(accountNumber, out var accNo)) {
|
|
|
|
+ errorCode = EPasaErrorCode.InvalidAccountNumber;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ epasa.Account = accNo;
|
|
|
|
+ var actualAccountChecksum = AccountHelper.CalculateAccountChecksum(accNo);
|
|
|
|
+
|
|
|
|
+ if (checksumDelim != null) {
|
|
|
|
+ // validate account checksum
|
|
|
|
+ if (!uint.TryParse(accountChecksum, out var accChecksum)) {
|
|
|
|
+ errorCode = EPasaErrorCode.AccountChecksumInvalid;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ if (accChecksum != actualAccountChecksum) {
|
|
|
|
+ errorCode = EPasaErrorCode.BadChecksum;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ epasa.AccountChecksum = actualAccountChecksum;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Encryption type
|
|
|
|
+ switch (payloadStartChar) {
|
|
|
|
+ case null:
|
|
|
|
+ break;
|
|
|
|
+ case "[":
|
|
|
|
+ if (payloadEndChar != "]") {
|
|
|
|
+ errorCode = EPasaErrorCode.MismatchedPayloadEncoding;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ epasa.PayloadType = epasa.PayloadType | PayloadType.Public;
|
|
|
|
+ break;
|
|
|
|
+ case "(":
|
|
|
|
+ if (payloadEndChar != ")") {
|
|
|
|
+ errorCode = EPasaErrorCode.MismatchedPayloadEncoding;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ epasa.PayloadType = epasa.PayloadType | PayloadType.RecipientKeyEncrypted;
|
|
|
|
+ break;
|
|
|
|
+ case "<":
|
|
|
|
+ if (payloadEndChar != ">") {
|
|
|
|
+ errorCode = EPasaErrorCode.MismatchedPayloadEncoding;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ epasa.PayloadType = epasa.PayloadType | PayloadType.SenderKeyEncrypted;
|
|
|
|
+ break;
|
|
|
|
+ case "{":
|
|
|
|
+ if (payloadEndChar != "}") {
|
|
|
|
+ errorCode = EPasaErrorCode.MismatchedPayloadEncoding;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ epasa.PayloadType = epasa.PayloadType | PayloadType.PasswordEncrypted;
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ throw new NotSupportedException($"Unrecognized start character '{payloadStartChar}'");
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Password
|
|
|
|
+ if (epasa.PayloadType.HasFlag(PayloadType.PasswordEncrypted)) {
|
|
|
|
+ if (payloadPasswordDelim == null) {
|
|
|
|
+ errorCode = EPasaErrorCode.MissingPassword;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ epasa.Password = PascalAsciiEncoding.Unescape(payloadPassword ?? "");
|
|
|
|
+ } else if (payloadPasswordDelim != null) {
|
|
|
|
+ errorCode = EPasaErrorCode.UnusedPassword;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Payload
|
|
|
|
+ if (payloadStartChar != null) {
|
|
|
|
+ if (payloadContent == null) {
|
|
|
|
+ epasa.Payload = string.Empty;
|
|
|
|
+ } else if (payloadContent.StartsWith("\"")) {
|
|
|
|
+ epasa.PayloadType = epasa.PayloadType | PayloadType.AsciiFormatted;
|
|
|
|
+ epasa.Payload = PascalAsciiEncoding.Unescape(payloadContent.Trim('"'));
|
|
|
|
+ } else if (payloadContent.StartsWith("0x")) {
|
|
|
|
+ epasa.PayloadType = epasa.PayloadType | PayloadType.HexFormatted;
|
|
|
|
+ epasa.Payload = payloadContent.Substring(2);
|
|
|
|
+ } else {
|
|
|
|
+ epasa.PayloadType = epasa.PayloadType | PayloadType.Base58Formatted;
|
|
|
|
+ epasa.Payload = payloadContent;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Payload Lengths
|
|
|
|
+ if (!EPasaHelper.IsValidPayloadLength(epasa.PayloadType, epasa.Payload)) {
|
|
|
|
+ errorCode = EPasaErrorCode.PayloadTooLarge;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Extended Checksum
|
|
|
|
+ var actualChecksum = EPasaHelper.ComputeExtendedChecksum(epasa.ToString(true));
|
|
|
|
+ if (extendedChecksumDelim != null) {
|
|
|
|
+ if (extendedChecksum != actualChecksum) {
|
|
|
|
+ errorCode = EPasaErrorCode.BadExtendedChecksum;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ epasa.ExtendedChecksum = actualChecksum;
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
```
|
|
```
|
|
|
|
|
|
-Some of the referenced methods can be found here:
|
|
|
|
|
|
+Some of the referenced methods can be found below:
|
|
|
|
|
|
```csharp
|
|
```csharp
|
|
|
|
+public static byte CalculateAccountChecksum(uint accountNo) {
|
|
|
|
+ var overflowSafeAccountNo = (ulong) accountNo;
|
|
|
|
+ return (byte)(overflowSafeAccountNo * 101 % 89 + 10);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+public static string ComputeExtendedChecksum(string text) {
|
|
|
|
+ if (text == null)
|
|
|
|
+ throw new ArgumentNullException(nameof(text));
|
|
|
|
+ var checksum = (ushort) (Hashers.MURMUR3_32(Encoding.ASCII.GetBytes(text), ExtendedChecksumMurMur3Seed) % 65536);
|
|
|
|
+ return EndianBitConverter.Little.GetBytes(checksum).ToHexString(true);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+public const int MaxPublicAsciiContentLength = 255;
|
|
|
|
+public const int MaxECIESAsciiContentLength = 144;
|
|
|
|
+public const int MaxAESAsciiContentLength = 223;
|
|
|
|
+public const int MaxPublicHexContentLength = 510 + 2;
|
|
|
|
+public const int MaxECIESHexContentLength = 288 + 2;
|
|
|
|
+public const int MaxAESHexContentLength = 446 + 2;
|
|
|
|
+public const int MaxPublicBase58ContentLength = 348;
|
|
|
|
+public const int MaxECIESBase58ContentLength = 196;
|
|
|
|
+public const int MaxAESBase58ContentLength = 304;
|
|
|
|
+public const uint ExtendedChecksumMurMur3Seed = 0;
|
|
|
|
+
|
|
|
|
+public static bool IsValidPayloadLength(PayloadType payloadType, string payloadContent) {
|
|
|
|
+ if (string.IsNullOrEmpty(payloadContent))
|
|
|
|
+ return true;
|
|
|
|
+
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.Public)) {
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.AsciiFormatted)) {
|
|
|
|
+ return PascalAsciiEncoding.Unescape(payloadContent).Length <= MaxPublicAsciiContentLength;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.HexFormatted)) {
|
|
|
|
+ return payloadContent.Length <= MaxPublicHexContentLength;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.Base58Formatted)) {
|
|
|
|
+ return payloadContent.Length <= MaxPublicBase58ContentLength;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // unknown encoding format
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.SenderKeyEncrypted) || payloadType.HasFlag(PayloadType.RecipientKeyEncrypted)) {
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.AsciiFormatted)) {
|
|
|
|
+ return PascalAsciiEncoding.Unescape(payloadContent).Length <= MaxECIESAsciiContentLength;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.HexFormatted)) {
|
|
|
|
+ return payloadContent.Length <= MaxECIESHexContentLength;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.Base58Formatted)) {
|
|
|
|
+ return payloadContent.Length <= MaxECIESBase58ContentLength;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // unknown encoding format
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.PasswordEncrypted)) {
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.AsciiFormatted)) {
|
|
|
|
+ return PascalAsciiEncoding.Unescape(payloadContent).Length <= MaxAESAsciiContentLength;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.HexFormatted)) {
|
|
|
|
+ return payloadContent.Length <= MaxAESHexContentLength;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (payloadType.HasFlag(PayloadType.Base58Formatted)) {
|
|
|
|
+ return payloadContent.Length <= MaxAESBase58ContentLength;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // unknown encoding format
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
|
|
- public static byte CalculateAccountChecksum(uint accountNo) {
|
|
|
|
- var overflowSafeAccountNo = (ulong) accountNo;
|
|
|
|
- return (byte)(overflowSafeAccountNo * 101 % 89 + 10);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public static string ComputeExtendedChecksum(string text) {
|
|
|
|
- if (text == null)
|
|
|
|
- throw new ArgumentNullException(nameof(text));
|
|
|
|
- var checksum = (ushort) (Hashers.MURMUR3_32(Encoding.ASCII.GetBytes(text), ExtendedChecksumMurMur3Seed) % 65536);
|
|
|
|
- return EndianBitConverter.Little.GetBytes(checksum).ToHexString(true);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public static bool IsValidPayloadLength(PayloadType payloadType, string payloadContent) {
|
|
|
|
- const int MaxPublicAsciiContentLength = 255;
|
|
|
|
- const int MaxECIESAsciiContentLength = 144;
|
|
|
|
- const int MaxAESAsciiContentLength = 223;
|
|
|
|
- const int MaxPublicHexContentLength = 510 + 2;
|
|
|
|
- const int MaxECIESHexContentLength = 288 + 2;
|
|
|
|
- const int MaxAESHexContentLength = 446 + 2;
|
|
|
|
- const int MaxPublicBase58ContentLength = 348;
|
|
|
|
- const int MaxECIESBase58ContentLength = 196;
|
|
|
|
- const int MaxAESBase58ContentLength = 304;
|
|
|
|
- const uint ExtendedChecksumMurMur3Seed = 0;
|
|
|
|
-
|
|
|
|
- if (string.IsNullOrEmpty(payloadContent))
|
|
|
|
- return true;
|
|
|
|
-
|
|
|
|
- if (payloadType.HasFlag(PayloadType.Public)) {
|
|
|
|
- if (payloadType.HasFlag(PayloadType.AsciiFormatted)) {
|
|
|
|
- return payloadContent.Length <= MaxPublicAsciiContentLength;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (payloadType.HasFlag(PayloadType.HexFormatted)) {
|
|
|
|
- return payloadContent.Length <= MaxPublicHexContentLength;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (payloadType.HasFlag(PayloadType.Base58Formatted)) {
|
|
|
|
- return payloadContent.Length <= MaxPublicBase58ContentLength;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // unknown encoding format
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (payloadType.HasFlag(PayloadType.SenderKeyEncrypted) || payloadType.HasFlag(PayloadType.RecipientKeyEncrypted)) {
|
|
|
|
- if (payloadType.HasFlag(PayloadType.AsciiFormatted)) {
|
|
|
|
- return payloadContent.Length <= MaxECIESAsciiContentLength;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (payloadType.HasFlag(PayloadType.HexFormatted)) {
|
|
|
|
- return payloadContent.Length <= MaxECIESHexContentLength;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (payloadType.HasFlag(PayloadType.Base58Formatted)) {
|
|
|
|
- return payloadContent.Length <= MaxECIESBase58ContentLength;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // unknown encoding format
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (payloadType.HasFlag(PayloadType.PasswordEncrypted)) {
|
|
|
|
- if (payloadType.HasFlag(PayloadType.AsciiFormatted)) {
|
|
|
|
- return payloadContent.Length <= MaxAESAsciiContentLength;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (payloadType.HasFlag(PayloadType.HexFormatted)) {
|
|
|
|
- return payloadContent.Length <= MaxAESHexContentLength;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (payloadType.HasFlag(PayloadType.Base58Formatted)) {
|
|
|
|
- return payloadContent.Length <= MaxAESBase58ContentLength;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // unknown encoding format
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // unknown encryption format
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
|
|
+ // unknown encryption format
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
```
|
|
```
|
|
|
|
|
|
Full source-code for the above is available [here][1].
|
|
Full source-code for the above is available [here][1].
|