E-PASA
Summary
A backwards compatible addressing scheme that enables an infinite address-space within account-based blockchains that use ordinal account numbers (such as PascalCoin). The usage of these extension addresses can transform a bounded finite address space into an unbounded infinite address space.
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 presentation-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 aunique 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).
-
They are deterministically dehydrated to into raw payloads.
-
No two fully check-summed 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 inPascal64 encoding:((){}[]:"<>
-
The following characters are escaped inPascalAscii 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 publicly 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
Pascal64String
These strings are used to denote an account names and conform to the following rules:
-
By definition, they mustnot start with a digit.
Account Name Validation
Account names in PascalCoin must be 3..64 characters in length. This validation rule is applied at the processing-tier level and not enforced by the EPASA grammar where a Pascal64 string is unbounded in size.
New Account Validation
EPASA is used to encode addresses for new accounts. An E-PASA of the form
@[1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2]
is used to denote a "Pay to Key" style transaction where payments are sent to a "new account" containing the key encoded in the payload.
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
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 |
Payload Type
In order for a recipient of an operation toautomatically anddeterministically 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 thePayloadType. 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 decoding. This capability is fundamental for using E-PASA as anaddress-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:
[Flags]
public enum PayloadType {
///
/// Payload encryption and encoding method not specified.
///
NonDeterministic = 0b00000000,
///
/// Unencrypted, public payload.
///
Public = 0b00000001,
///
/// ECIES encrypted using recipient accounts public key.
///
RecipientKeyEncrypted = 0b00000010,
///
/// ECIES encrypted using sender accounts public key.
///
SenderKeyEncrypted = 0b00000100,
///
/// AES encrypted using pwd param
///
PasswordEncrypted = 0b00001000,
///
/// Payload data encoded in ASCII
///
AsciiFormatted = 0b00010000,
///
/// Payload data encoded in HEX
///
HexFormatted = 0b00100000,
///
/// Payload data encoded in Base58
///
Base58Formatted = 0b01000000,
///
/// E-PASA addressed by account name (not number).
///
AddressedByName = 0b10000000,
}
The values are interpreted as follows:
Value | Interpretation |
00000000 | Non-deterministic (requires manual decoding by receiver andnot 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-77
|
Account 123456-77 (backwards compatible with current addresses) |
pascalcoin-foundation
|
Account with name 'pascalcoin-foundation' no payload |
my-favorite-exchange("[email protected]")
|
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-77
|
Account 123456-77 (backwards compatible) |
123456-77["Hello World!"]
|
With public ASCII payload "Hello World!" without checksum protection |
123456-77["Hello World!"]:10cb
|
Checksum protected |
123456-77("Hello World!"):7ba2
|
ECIES encrypted usingrecipients public key |
123456-77<"Hello World!">:b51f
|
ECIES encrypted usingsenders public key |
123456-77{"Hello World!":!43lp|-d|a%@#!}
|
AES256 encrypted payload using password!43lp|-d|a%@#! |
With Hexadecimal payloads
7-44[0x416c70686124]
|
Account 77-44 with unencrypted (public) hexadecimal payloadwithout protection |
77-44[0x416c70686124]:10cb
|
Checksum protected |
77-44(0x416c70686124):7ba2
|
ECIES encrypted usingrecipients public key (and checksum protected) |
77-44<0x416c70686124>:b51f
|
ECIES encrypted usingsenders public key (and checksum protected) |
77-44{0x416c70686124:!43lp-da%@#!}
|
AES encrypted using password!43lp-da%@#! |
With Base58 payloads
SEYstWetqTFn5Au4m4GFg7xJaNVN2]
|
Pay To Key style transaction with Base58 encoded public keywithout checksum protection |
@[1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2]:a054
|
Pay To Key style transaction with Base58 encoded public keywith checksum protection |
77-44[1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2]
|
Account 77-44 with unencrypted (public) Base58 payload (bitcoin address)without checksum protection |
77-44[1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2]:10cb
|
Checksum protected |
77-44(1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2):7ba2
|
ECIES encrypted usingrecipients public key |
77-44<1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2>:b51f
|
ECIES encrypted usingsenders public key |
77-44{1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2:!43lp-da%@#!}
|
AES encrypted using password!43lp-da%@#! |
Special Cases
-72["Message with all escaped chars \\\"\} here"]
|
Public ANSI stringMessage with all escaped chars \"} here |
999-72
|
Non-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 passwordpwd but ignoredpwd but ignored |
my-account-name
|
Non-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 passwordpwd but ignored |
999-72{"Hello":Funny\"Pwd}
|
AES encrypted using escaped passwordFunny"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 PayloadTypedoes require a hard-forkexcept 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|!|@|#|\$|%|\^|&|\*|\\\(|\\\)|-|\+|\\\{|\\\}|\\\[|\\]|_|\\:|\\"|`|\||\\<|\\>|,|\.|\?|/|~)*))(?:(?[\[\(<\{])(?"( |!|\\"|#|\$|%|&|'|\\\(|\\\)|\*|\+|,|-|\.|/|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
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:
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 .
A recursive-descent implementation can be found here .