Browse Source

PIP-0027: final edits, bug fixes

Herman Schoenfeld 6 years ago
parent
commit
08a5e18539
1 changed files with 241 additions and 157 deletions
  1. 241 157
      PIP/PIP-0027.md

+ 241 - 157
PIP/PIP-0027.md

@@ -53,13 +53,13 @@ An Extended PASA is defined by the below EBNF grammar:
     ReceiverEncPayload = "(", [ Payload ], ")" ;
     SenderEncPayload   = "<", [ Payload ], ">" ;
     PasswordEncPayload = "{", [ Payload ], ":", [ Password ], "}" ;
-    Payload            = ( """, SafeAnsiString, """ | "0", "x", HexString | Base58String ) ;
-    Password           = SafeAnsiString
-    SafeAnsiString     = SafeAnsiChar, { SafeAnsiChar } ;
-    SafeAnsiChar       = (" " | "!" | 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     = SafePascal64Char, { Pascal64Char } ;
-    Pascal64Char       = (Digit | SafePascal64Char)
-    SafePascal64Char   = ( "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, ">" | "," | "." | "?" | "/" | "~" ) ; 
+    Payload            = ( """, PascalAsciiString, """ | "0", "x", HexString | Base58String ) ;
+    Password           = PascalAsciiString
+    PascalAsciiString  = PascalAsciiChar, { PascalAsciiChar } ;
+    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 } ;
+    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, ">" | "," | "." | "?" | "/" | "~" ) ; 
     HexString          = HexByte { HexByte } ;
     HexByte            = HexNibble, HexNibble ;
     HexNibble          = ( Digit | "a" | "b" | "c" | "d" | "e" | "f" ) ;       (* no uppercase hex allowed *)
@@ -74,7 +74,7 @@ An Extended PASA is defined by the below EBNF grammar:
 
 **NOTES**: 
  * Text payload and passwords are restricted to ANSI charset subset range 32..126
- * The following characters are escaped in Pascal64 and SafeAnsi strings): **:**, **\\**, **"**, **[**, **]**, **(**, **), **<**, **>**,**{**, **}**
+ * The following characters are escaped in Pascal64 and PascalAscii strings): **:**, **\\**, **"**, **[**, **]**, **(**, **), **<**, **>**,**{**, **}**
  * Escape character is always: **\\**
 
 The above rules can be interpreted as follows:
@@ -92,9 +92,9 @@ The above rules can be interpreted as follows:
 | PasswordEncPayload | A payload which is AES256 encrypted using the specified password                                              |
 | Payload            | The actual payload data, specified it an well-defined encoding                                                |
 | ExtendedChecksum   | A checksum of all the preceding text in the E-PASA (necessary to prevent typo-errors)                         |
-| Password           | The password used in PasswordEndPayload. Must be specified as a SafeAnsiString (chars 32..126)                |
+| Password           | The password used in PasswordEndPayload. Must be specified as a PascalAsciiString (chars 32..126)             |
 | Pascal64String     | An ANSI string involving a limited subset used for account names (cannot start with a digit)                  |
-| SafeAnsiString     | An ANSI string involvolving subset characters 32..126                                                         |
+| PascalAsciiString  | An ANSI string involvolving subset characters 32..126                                                         |
 | Base58String       | A Base58-encoded string. This is used for specifying public keys, and hashes of public keys                   |
 | HexString          | A hexadecimal-encoded string prefixed with a 0x. Every byte specified by two hexdigits, lower-case            |
 
@@ -372,158 +372,242 @@ For Layer-2 applications the ability for a receiver to auto-decode the E-PASA vi
 
 The following regex parses an e-pasa:
 ```
-((?<AccountNumber>[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
 
 ```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 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["AccountChecksum"].Success ? match.Groups["AccountChecksum"].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;
-		} else {
-			// Account Number
-			if (!uint.TryParse(accountNumber, out var accNo)) {
-				errorCode = EPasaErrorCode.AccountNumberTooLong;
-				return false;
-			}
-			epasa.PayloadType = epasa.PayloadType ^ PayloadType.AddressedByName;
-			epasa.Account = accNo;
-
-			if (checksumDelim != null) {
-				if (!uint.TryParse(accountChecksum, out var accChecksum)) {
-					errorCode = EPasaErrorCode.AccountChecksumInvalid;
-					return false;
-				}
-				if (!AccountHelper.IsValidAccountChecksum(epasa.Account.Value, accChecksum)) {
-					errorCode = EPasaErrorCode.BadChecksum;
-					return false;
-				}
-				epasa.AccountChecksum = accChecksum;
-			}
-		}
-
-		// 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;
-			} 
-			epasa.Payload = payloadContent;
-		}
-
-		// Payload Lengths
-		if (!EPasaHelper.IsValidPayloadLength(epasa.PayloadType, epasa.Payload)) {
-			errorCode = EPasaErrorCode.PayloadTooLarge;
-			return false;
-		}
-
-		// Extended Checksum
-		if (extendedChecksumDelim != null) {
-			if (checksumDelim == null) {
-				errorCode = EPasaErrorCode.MissingAccountChecksum;
-				return false;
-			}
-			if (!EPasaHelper.IsValidExtendedChecksum(epasa.ToString(true), epasa.ExtendedChecksum)) {
-				errorCode = EPasaErrorCode.BadExtendedChecksum;
-				return false;
-			}
-			epasa.ExtendedChecksum = extendedChecksum;
-		}
-		return true;
-	}
+Some of the referenced methods can be found here:
+
+```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 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;
+        }
 ```
 
 Full source-code for the above is available [here][1]. 
@@ -536,6 +620,6 @@ A recursive-descent implementation can be found [here][2].
 1. [C# Regex Parser][1]
 2. [C# Recursive-Descent Parser][2]
 
-[1]: https://github.com/Sphere10/NPascalCoin/blob/master/src/NPascalCoin/Common/Parsing/RegexEPasaParser.cs
-[2]: https://github.com/Sphere10/NPascalCoin/blob/master/src/NPascalCoin/Common/Parsing/RecursiveDescentEPasaParser.cs
+[1]: https://github.com/Sphere10/NPascalCoin/blob/master/src/NPascalCoin/Common/Text/RegexEPasaParser.cs
+[2]: https://github.com/Sphere10/NPascalCoin/blob/master/src/NPascalCoin/Common/Text/RecursiveDescentEPasaParser.cs