PIP: PIP-0027
  Title: E-PASA: Infinite Address-Space (via Layer-2)
  Type: Protocol, Front-End
  Impact: Hard-Fork
  Author: Herman Schoenfeld 
  Comments-URI: https://discord.gg/sJqcgtD  (channel #pip-0027)
  Status: Active
  Created: 2019-02-11
## Summary This PIP proposes a backwards compatible addressing scheme that enables an infinite address-space within PascalCoin. The usage of these extension addresses can be employed immediately by existing infrastructure such as wallets and exchanges and in future Layer-2 dapps. ## Motivation PascalCoin currently allows users to send/receive operations between accounts using their account numbers (PASA). These account numbers are a limited (and commoditized) resource which form a finite address-space (note: this is fundamental to SafeBox design and it's infinite-scaling capability). PascalCoin will provide an infinite address-space (similar to other other crypto-currencies) via "decentralized custodial accounts" which are Layer-2 dapps governed via a Layer-2 Proof-of-Stake overlay network. Before rolling out this Layer-2 infrastructure, PascalCoin first needs to establish an addressing-scheme for this infinite address-space. This PIP provides one such scheme. Additionally, this scheme can also be immediately employed at the presention-layer to greatly simplify exchange integrations and payload-based payments. ## Specification Layer-2 addresses will be herein referred to as Extended PASA, or E-PASA for short. An E-PASA has the following unique characteristics: * Each E-PASA is a **unique** identifier. * An E-PASA is encoded into the raw network payload of operations. * There is a 1-1 mapping between E-PASA and their raw payload form. * They are deterministically dehydrated to into raw payloads. * They are deterministically hydrated from raw payloads (**note**: requires V5 protocol). * No two fully checksummed E-PASA's can refer to the same logical address. ### Extended PASA format (E-PASA) An Extended PASA is defined by the below EBNF grammar: ``` EPASA = PASA, [ ExtendedAddress ], [ ':', ExtendedChecksum ] ; PASA = ( AccountName | AccountNumber ) ; AccountName = Pascal64String ; AccountNumber = Integer, "-", Checksum ; Checksum = Digit, Digit ; ExtendedChecksum = HexByte, HexByte ; ExtendedAddress = ( PublicPayload | ReceiverEncPayload | SenderEncPayload | PasswordEncPayload ) ; PublicPayload = "[", [ Payload ], "]" ; ReceiverEncPayload = "(", [ Payload ], ")" ; SenderEncPayload = "<", [ Payload ], ">" ; PasswordEncPayload = "{", [ Payload ], ":", [ Password ], "}" ; 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, "<" | EscapeChar, ">" | "," | "." | "?" | "/" | "~" ) ; HexString = HexByte { HexByte } ; HexByte = HexNibble, HexNibble ; HexNibble = ( Digit | "a" | "b" | "c" | "d" | "e" | "f" ) ; (* no uppercase hex allowed *) Base58String = Base58Char, { Base58Char } ; Base58Char = ( NaturalDigit | Base58UpperChar | Base58LowerChar ) ; Base58UpperChar = ( "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "J" | "K" | "L" | "M" | "N" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" ) ; (* missing I, O *) Base58LowerChar = ( "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" ) ; (* missing l *) Integer = NaturalDigit, { Digit } ; Digit = ( "0" | NaturalDigit ) ; NaturalDigit = ( "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ) ; EscapeChar = "\" ; ``` **NOTES**: * Text payload and passwords are restricted to ANSI charset subset range 32..126 * The following characters are escaped in **Pascal64** encoding: **(** **(** **)** **{** **}** **[** **]** **:** **"** **<** **>** * The following characters are escaped in **PascalAscii** encoding: **"** **(** **)** **:** **<** **>** **[** **\\** **]** **{** **}** * Escape character is always: **\\** The above rules can be interpreted as follows: | Rule | Interpretation | | -----------------: | :------------------------------------------------------------------------------------------------------------ | | EPASA | This is a layer-2 address, fully backwards compatible as Layer-1 address | | PASA | This is the standard layer-1 address of the receiver account (account number or account name) | | Checksum | This is the standard layer-1 address checksum | | ExtendedAddress | The optional extra text that forms part of layer-2 address (payload specification) | | PublicPayload | A payload which is not encrypted and publically visible | | ReceiverEncPayload | A payload which is ECIES encrypted using receivers public key (the PASA portion specifies receiver) | | SenderEncPayload | A payload which is ECIES encrypted using the senders public key (only sender can decrypt EPASA) | | 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 PascalAsciiString (chars 32..126) | | Pascal64String | An ANSI string involving a limited subset used for account names (cannot start with a digit) | | 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 | #### Validation Rules #### AccountNumber Checksum Layer-1 account checkum must be the following number: ``` Checksum = ((AccountNumber*101) MOD 89) + 10 ``` **NOTE** AccountNumber above denotes the integer portion of the string. **IMPORTANT**: Whilst the E-PASA grammar allows optional PASA checksum, this is purely for convenience. Implementations are expected to automatically fill-in the checksum if not specified in the input. #### Pascal64String These strings are used to denote an account names and conform to the following rules. - By definition, they must **not** start with a digit. - String length must between 3..64 inclusive. #### Extended Checksum In order to avoid data entry errors, the EPASA is checksummed via an Extended Checksum. In short, the Extended Checksum is simply the 16-bit MurMur3 hash of all preceding text. Formally, the EPASA checksum is defined as follows: ``` PayloadChecksum = ToHexStringLE ( CastToUINT16( MurMur3( ToAsciiBytes ( PASA ++ ExtendedAddress ) ) MOD 65536 ) ) where ToAsciiBytes = converts ASCII string argument into raw byte form, character by character (no endianness concerns here) MurMur3 = performs 32bit MurMur3 hash of the byte array argument CastToUINT16 = casts the integer argument into to a 16bit unsigned integer (should never overflow due to modulo 65536) ToHexStringLE = converts the 16bit unsigned integer argument into 4 hexadecimal characters in little-endian ``` **IMPORTANT**: Whilst the E-PASA grammar allows optional Extended Checksums, this is purely for convenience. Implementations are expected to automatically fill-in the checksum if not specified in the input. #### Payload Lengths The following validation rules must be applied to Payload lengths | Payload Type | Encryption Mode | Byte-form Length | E-PASA string-form length | | :------------------- | :--------------- | :------------------ | :------------------------- | | ASCII | None (Public) | 255 | 255 | | ASCII | ECIES | 144 | 144 | | ASCII | AES | 223 | 223 | | Hexadecimal | None (Public) | 255 | 510+2 | | Hexadecimal | ECIES | 144 | 288+2 | | Hexadecimal | AES | 223 | 446+2 | | Base58 | None (Public) | 255 | 348 | | Base58 | ECIES | 144 | 196 | | Base58 | AES | 223 | 304 | **NOTE:** +2 accounts for "0x" prefix for hexadecimal strings #### Payload Type In order for a recipient of an operation to **automatically** and **deterministically** decode an E-PASA from a raw network payload, the PascalCoin protocol needs additional data to describe the Payload. This PIP proposes pre-fixing all Payloads in operations with a single-byte value called the **PayloadType**. The PayloadType describes the encryption and encoding of the Payload. PayloadType will allow E-PASA's to be deterministically decoded by the recipient and prevent ambiguous decodings. This capability is fundamental for using E-PASA as an *address-space* since uniqueness requires a 1-1 mapping. With this feature, Layer-2 apps can safely use E-PASA as Layer-2 addresses. #### Payload Type Specification: ```csharp [Flags] public enum PayloadType { /// /// Payload encryption and encoding method not specified. /// NonDeterministic = 0x00000000, /// /// Unencrypted, public payload. /// Public = 0x00000001, /// /// ECIES encrypted using recipient accounts public key. /// RecipientKeyEncrypted = 0x00000010, /// /// ECIES encrypted using sender accounts public key. /// SenderKeyEncrypted = 0x00000100, /// /// AES encrypted using pwd param /// PasswordEncrypted = 0x00001000, /// /// Payload data encoded in ASCII /// AsciiFormatted = 0x00010000, /// /// Payload data encoded in HEX /// HexFormatted = 0x00100000, /// /// Payload data encoded in Base58 /// Base58Formatted = 0x01000000, /// /// E-PASA addressed by account name (not number). /// AddressedByName = 0x10000000, } ``` The values are interpreted as follows: | Value | Interpretation | | -------: | :------------------------------------------------------------------------------ | | 00000000 | Non-deterministic (requires manual decoding by receiver and **not** an E-PASA) | | 00000001 | Public payload (E-PASA shows empty payload) | | 00000010 | ECIES-Sender encrypted payload (E-PASA shows empty payload) | | 00000100 | ECIES-Receiver encrypted payload (E-PASA shows empty payload) | | 00001000 | AES encrypted payload (E-PASA shows empty payload) | | 00010001 | Public payload in ASCII encoding | | 00010010 | ECIES-Sender encrypted payload in ASCII encoding | | 00010100 | ECIES-Receiver encrypted payload in ASCII encoding | | 00011000 | AES encrypted payload in ASCII encoding | | 00100001 | Public payload in hexadecimal encoding | | 00100010 | ECIES-Sender encrypted payload in hexadecimal encoding | | 00100100 | ECIES-Receiver encrypted payload in hexadecimal encoding | | 00101000 | AES encrypted payload in hexadecimal encoding | | 01000001 | Public payload in Base58 encoding | | 01000010 | ECIES-Sender encrypted payload in Base58 encoding | | 01000100 | ECIES-Receiver encrypted payload in Base58 encoding | | 01001000 | AES encrypted payload in Base58 encoding | | 10000000 | Non-deterministic (E-PASA shows Account Name) | | 10000001 | Public payload (E-PASA shows Account Name and empty payload) | | 10000010 | ECIES-Sender encrypted payload (E-PASA shows AccountName and empty payload) | | 10000100 | ECIES-Receiver encrypted payload (E-PASA shows AccountName and empty payload) | | 10001000 | AES encrypted payload (E-PASA uses AccountName and empty payload) | | 10010001 | Public payload in ASCII encoding (E-PASA shows Account Name) | | 10010010 | ECIES-Sender encrypted payload in ASCII encoding (E-PASA shows Account Name) | | 10010100 | ECIES-Receiver encrypted payload in ASCII encoding (E-PASA shows Account Name) | | 10011000 | AES encrypted payload in ASCII encoding (E-PASA shows AccountName) | | 10100001 | Public payload in hexadecimal encoding (E-PASA shows AccountName) | | 10100010 | ECIES-Sender encrypted payload in hexadecimal encoding (E-PASA shows AccountName)| | 10100100 | ECIES-Receiver encrypted payload in hexadecimal encoding (E-PASA shows AccountName)| | 10101000 | AES encrypted payload in hexadecimal encoding (E-PASA shows AccountName) | | 11000001 | Public payload in Base58 encoding (E-PASA shows AccountName) | | 11000010 | ECIES-Sender encrypted payload in Base58 encoding (E-PASA shows AccountName) | | 11000100 | ECIES-Receiver encrypted payload in Base58 encoding (E-PASA shows AccountName) | | 11001000 | AES encrypted payload in Base58 encoding (E-PASA shows AccountName) | ## 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
E-PASA Description
123456-77Account 123456-77 (backwards compatible with current addresses)
pascalcoin-foundationAccount with name 'pascalcoin-foundation' no payload
my-favorite-exchange("herman@email.com")An account called "my-favorite-exchange" with a recipient-encrypted payload (e.g. an exchange deposit address where only exchange can see payload, used as a user ID)
### With ASCII payloads
E-PASA Description
123456-77Account 123456-77 (backwards compatible)
123456-77["Hello World!"]With public ASCII payload "Hello World!" without checksum protection
123456-77["Hello World!"]:10cbChecksum protected
123456-77("Hello World!"):7ba2ECIES encrypted using recipients public key
123456-77<"Hello World!">:b51fECIES encrypted using senders public key
123456-77{"Hello World!":!43lp|-d|a%@#!}AES256 encrypted payload using password !43lp|-d|a%@#!
### With Hexadecimal payloads
E-PASA Description
77-44[0x416c70686124]Account 77-44 with unencrypted (public) hexadecimal payload without protection
77-44[0x416c70686124]:10cbChecksum protected
77-44(0x416c70686124):7ba2ECIES encrypted using recipients public key (and checksum protected)
77-44<0x416c70686124>:b51fECIES encrypted using senders public key (and checksum protected)
77-44{0x416c70686124:!43lp-da%@#!}AES encrypted using password !43lp-da%@#!
### With Base58 payloads
E-PASA Description
77-44[1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2]Account 77-44 with unencrypted (public) Base58 payload (bitcoin address) without checksum protection
77-44[1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2]:10cbChecksum protected
77-44(1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2):7ba2ECIES encrypted using recipients public key
77-44<1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2>:b51fECIES encrypted using senders public key
77-44{1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2:!43lp-da%@#!}AES encrypted using password !43lp-da%@#!
### Special Cases
E-PASA Description
999-72["Message with all escaped chars \\\"\} here"]Public ANSI string Message with all escaped chars \"} here
999-72Non-deterministic, raw network-level payload bytes ignored
999-72[]Empty Public payload, raw network-level payload bytes ignored
999-72()Empty ECIES payload, raw network-level payload bytes encrypted with receivers key but ignored
999-72<>Empty ECIES payload, raw network-level payload bytes encrypted with senders key but ignored
999-72{:}Empty AES payload, raw network-level payload bytes AES encryped but ignored
999-72{:pwd}Empty AES payload, raw network-level payload bytes AES encryped with password pwd but ignoredpwd but ignored
my-account-nameNon-deterministic, raw network-level payload bytes ignored
my-account-name[]Empty public payload, raw network-level payload bytes ignored
my-account-name()Empty ECIES payload, raw network-level payload bytes encrypted with receivers key but ignored
my-account-name<>Empty ECIES payload, raw network-level payload bytes encrypted with senders key but ignored
my-account-name{:}Empty AES payload, raw network-level payload bytes encrypted with empty password but ignored
my-account-name{:pwd}Empty AES payload, raw network-level payload bytes encrypted with password pwd but ignored
999-72{"Hello":Funny\"Pwd}AES encrypted using escaped password Funny"Pwd
999-72{"Hello":\\\"\}}AES encrypted using escaped password \"}
## Rationale The design approach was to remain backwards compatible so that EPASA can replace "account" in existing JSON APIs. The caller need not specify Payloads anymore since the EPASA can contain the Payload. ## Backwards Compatibility This PIP is generally backwards compatible and does not require a hard-fork activation for using E-PASA as a convention. For Layer-2 applications the ability for a receiver to auto-decode the E-PASA via PayloadType **does require a hard-fork** **except** although this does not prevent usage of E-PASA for other purposes. ## Acknowledgements * Ugochukwu Mmaduekwe for assistance developing payload length validation rules * Benjamin Ansbach for regular feedback, assistance and insightful suggestions * UrbanCohort for elegancy-improving suggestion ## Reference Implementation The following regex parses an e-pasa: ``` ((?(0|[1-9]\d+))(?:(?-)(?\d{2}))?|(?(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}))(?:(?[\[\(<\{])(?"( |!|\\"|#|\$|%|&|'|\\\(|\\\)|\*|\+|,|-|\.|/|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]+)?(?:(?:){1}(?( |!|\\"|#|\$|%|&|'|\\\(|\\\)|\*|\+|,|-|\.|/|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|\\\{|\||\\\}|~)+)?)?(?[]\)>\}]))?(?:(?:)(?[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 = 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 below: ```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; } // unknown encryption format return false; } ``` Full source-code for the above is available [here][1]. A recursive-descent implementation can be found [here][2]. ## Links 1. [C# Regex Parser][1] 2. [C# Recursive-Descent Parser][2] [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