Skip to content

Instantly share code, notes, and snippets.

@tevador
Last active January 1, 2023 22:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tevador/500d5d32d5ecc73b56997e12a9d2b20e to your computer and use it in GitHub Desktop.
Save tevador/500d5d32d5ecc73b56997e12a9d2b20e to your computer and use it in GitHub Desktop.

Jamtis URI Schemes

This specification introduces new URI schemes compatible with the Jamtis addressing scheme [1].

The URI encoding follows RFC 3986 [2] and has the following format:

SCHEME ":" PATH

The URIs are typically used to transfer information between devices. The PATH segment of the URI only uses characters available in the alphanumeric encoding of QR codes [3] for maximum space efficiency. The URI should be converted to uppercase format when encoded in QR codes.

1. Payment request URI

The payment URI is used to request a payment to be made. The URI scheme is "xmrpay" to disambiguate it from the legacy "monero" scheme [4].

SCHEME = "xmrpay"

The URI path encodes one or more invoices separated by the + character.

PATH = INVOICE [ "+" INVOICE ...]

1.2 Invoice encoding

The invoice is encoded as a base32 string using the Jamtis base32 alphabet [5]. Each invoice starts with the prefix xmri.

INVOICE = "xmri" <amount> <address> <version> <tagged-fields> <checksum>

The maximum permitted length of an invoice is 994 characters, which corresponds to the maximum length when the checksum algorithm can detect 5 errors.

1.3 Human-readable amount

The amount is encoded in human-readable form as a decimal integer value (using digits 0-9) with an optional unit suffix. The payment amount is calculated by applying a multiplier to the integral part depending on the suffix.

suffix multiplier unit
none 1 monero
m 10-3 millinero
u 10-6 micronero
n 10-9 nanonero
p 10-12 piconero

The amount should be encoded with the shortest possible representation (i.e. using the suffix with the largest multiplier or no suffix). For example, 0.0123 XMR is encoded as 12300u.

The amount may be set to an empty string to indicate an unspecified payment amount.

1.4 Address

The address is a base32-encoded Jamtis address starting with the prefix xmra [1].

1.5 Version

Version is an integer encoded as a posvarint-16 (see Appendix A). The current version value is 1. The software decoding the invoice shall abort if the version value is higher than the expected value.

1.6 Tagged fields

The invoice can contain zero or more tagged fields with the following format:

TAGGED-FIELD = <tag> <length> <value>
  • tag is a 1-character tag that encodes the field type
  • length is the size of the data field (in multiples of 5 bits) encoded as a posvarint-16 (see Appendix A)
  • value is the content of the field (always multiple of 5 bits)

The following tagged fields are supported:

tag length field value
n variable recipient's name text (see Appendix B)
d variable payment description text (see Appendix B)
e 7 payment expiration time integer (35-bit, little endian)
m variable domain name text (see Appendix B)
k 51 public key binary (255-bit, see Appendix C)
s 76 signature binary (380-bit, see Appendix C)

The length must be included even for the constant-length fields for compatibility reasons.

The tagged fields must be sorted in the order they are listed in the table above. Duplicates are not allowed.

1.6.1 Recipient's name

This is an arbitrary value that can be displayed to the user by the wallet software as the recipient's name. The recipient's name can also be loaded using the DNS-based lookup (§ 1.8).

1.6.2 Payment description

This is an arbitrary value that can be displayed to the user by the wallet software as the payment description.

1.6.3 Payment expiration

This is the date and time when the invoice will expire. The wallet software should warn the user that the invoice has expired. The value is encoded as the number of seconds since the Unix epoch, as a 35-bit unsigned integer. The 5-bit segments are ordered from the least significant bit (little-endian). The timestamp can encode dates until the year 3058.

1.6.4 Domain name

This field specifies the domain name that has issued the invoice. It is used for the DNS-based lookup (§ 1.8).

1.6.5 Public key

This field encodes an ed25519 public key used to verify the invoice signature. For efficiency reasons, the public key is encoded only as the y coodinate. The x coodinate is implicitly chosen to be the even-parity value. The key generation procedure is specified in Appendix C. The public key can also be loaded using the DNS-based lookup (§ 1.8).

1.6.6 Signature

The optional signature signs all preceding data (the signature field is always the last tagged field of the invoice). The signature format is a short Schnorr signature [7] described in Appendix C, encoded in 380 bits (76 base32 characters).

The signed data are constructed by converting all base32 characters of the invoice preceding the signature field to binary and padding with 0-bits to the next byte-boundary.

The signature is only validated if a public key is available, either loaded using the DNS-based lookup (§1.8) or encoded in the field k. If the signature is invalid or can't be validated, a warning should be displayed.

1.7 Checksum

Accidental corruption of the invoice string is protected by an 8-character checksum that is calculated with the same algorithm as the Jamtis address checksum [6].

1.8 DNS-based lookup

If the invoice contains the m field (domain name) and the s field (signature), the software reading the invoice should perform the following DNS lookup procedure.

1.8.1 DNS Lookup

  1. The TXT records associated with the domain name are fetched.
  2. The DNSSEC signature is verified. If DNSSEC is not configured or the chain of trust is invalid, the lookup procedure is aborted.
  3. The TXT records are searched for records matching the "xmrpay-info" scheme described in §1.8.2. There should be exactly one matching record, otherwise the procedure is aborted.
  4. The TXT record is parsed and the tagged fields in the record are inserted into the tagged field collection of the invoice. The fields from the TXT record take priority over the records encoded in the invoice.

1.8.2 TXT record format

The TXT record has the following URI format:

"xmrpay-info:" <version> [tagged-field-n] <tagged-field-k> <checksum>

The version field follows the rules from §1.5. The version field can be optionally followed by the tagged field n (recipient's name, §1.6.1) and the next item must be the tagged field k (public key, §1.6.5). The checksum is calculated as in §1.7 and covers all characters after the colon.

The fields loaded using the DNS lookup override the n and k tagged fields present in the invoice. If the DNS lookup fails, the fields present in the invoice are used as a backup.

1.9 Examples

Payment of 239.39014 XMR with the description "donation" to an example Jamtis address:

xmrpay:xmri239390140uxmra1mj0b1977bw3ympyh2yxd7hjymrw8crc9kin0dkm8d3wdu8jdhf3fkd
pmgxfkbywbb9mdwkhkya4jtfn0d5h7s49bfyji1936w19tyf3906ypj09n64runqjrxwp6k2s3phxwm6
wrb5c0b6c1ntrg2muge0cwdgnnr7u7bgknya9arksrj0re7whkckh51ikxddtdx0natix0ntxau2sp8

This URI fits onto a 49x49 QR code (1320 bits).

The legacy URI equivalent would be:

monero:xmra1mj0b1977bw3ympyh2yxd7hjymrw8crc9kin0dkm8d3wdu8jdhf3fkdpmgxfkbywbb9md
wkhkya4jtfn0d5h7s49bfyji1936w19tyf3906ypj09n64runqjrxwp6k2s3phxwm6wrb5c0b6c1ntrg
2muge0cwdgnnr7u7bgknya9arksrj0re7whkckh51ik?tx_amount=239.39014&tx_description=d
onation

The legacy URI needs a 57x57 QR code (1976 bits).

2. Wallet keys URI

The wallet keys URI is used to export and import wallet private keys. The URI scheme is "xmrwallet".

SCHEME = "xmrwallet"

The URI path is encoded as a base32 string with the prefix xmrw.

PATH = "xmrw" <version> <tier-tag> <data> <checksum>

2.1 Version

The version is encoded as a decimal number using digits 0-9. The current version value is 1.

2.2 Wallet tier and data

Wallet tier tag data total length
AddrGen g sga, Dua, Dfr, Ks 219
FindReceived f birthday, dfr 68
ViewReceived r birthday, sga, dua, dfr, Ks 221
ViewAll a birthday, kvb, Ks 119

The private and public keys are serialized according to the Jamtis notation [8]. The wallet birthday is serialized as a 10-bit number in the same format that is used by the Polyseed mnemonic phrase [9].

The "Master" wallet tier is not supported by the wallet keys URI. The mnemonic seed should be used instead.

2.3 Checksum

The checksum is calculated over the whole PATH segment and it uses the same algorithm as Jamtis [6].

2.4 Examples

TODO

References

  1. https://gist.github.com/tevador/50160d160d24cfc6c52ae02eb3d17024
  2. https://www.ietf.org/rfc/rfc3986.txt
  3. https://en.wikipedia.org/wiki/QR_code#Encoding
  4. https://github.com/monero-project/monero/wiki/URI-Formatting#tx-scheme
  5. https://gist.github.com/tevador/50160d160d24cfc6c52ae02eb3d17024#35-base32-encoding
  6. https://gist.github.com/tevador/50160d160d24cfc6c52ae02eb3d17024#63-checksum
  7. https://eprint.iacr.org/2019/1105.pdf
  8. https://gist.github.com/tevador/50160d160d24cfc6c52ae02eb3d17024#3-notation
  9. https://github.com/tevador/polyseed#wallet-birthday

Appendix A: posvarint-16 encoding

Posvarint-16 is a variable-length encoding of a positive (non-zero) integral value using base32. The integer is encoded as a sequence of base32 characters (5 bits each), where the least significant 4 bits of each character is a little-endian base-16 digit of the integral, and the most significant bit indicates the next base32 character is a continuation of the current integral. The values of digits are offset by 1, i.e. digits {0}, {1}, ... {15} have values {1}, {2}, ... {16}.

values number of digits
1-16 1
17-272 2
273-4368 3

Examples:

number digits base32 decoding
7 (6) e =7*1
17 (16,0) kx =1*1+1*16
32 (31,0) 9x =16*1+1*16
33 (16,1) km =1*1+2*16
200 (23,11) yf =8*1+12*16

Appendix B: Text encoding

Text can be represented in one of two encoding modes: Unicode or Alphanumeric. The encoder should select the encoding that produces fewer number of characters for a given string.

B.1 Unicode encoding

This mode can encode any Unicode string. The text is converted to a UTF8 byte sequence and then encoded using base32. For alphanumeric strings, this will typically be less efficient than the alphanumeric encoding unless the text needs many escape sequences.

B.2 Alphanumeric encoding

The alphanumeric mode starts with the character t, followed by a sequence of base32 characters. The character t has a binary value of 10100 and no valid UTF8 string can start with this bit sequence.

In the alphanumeric mode, all base32 characters except of x represent themselves. The character x starts an escape sequence. The following escape sequences are supported in the alphanumeric mode:

escape value
xx (space)
xk x
x0 o
x1 l
xw v
x2 z
xd .
xh -
xe !
xq ?
xm ,
xu (the next character is uppercase)
xc .co
xg .org
xn .net

Examples:

text encoded
example.com texkampx1excm
Happy birthday! txuhappyxxbirthdayxe
order no. 123456789 tx0rderxxnx0xd123456789
James Smith txujamesxxxusmith

Appendix C: Short Schnorr signature

This signature scheme uses the ed25519 elliptic curve and consist of 3 functions: KeyGen, Sign and Verify. The value ℓ = 2252 + 27742317777372353535851937790883648493 is the order of the prime subgroup of the elliptic curve.

The fuction Hx() refers to the Blake2b hash function with an output length of x bytes. If two parameters are provided, hashing is done in the keyed mode and the first parameter is the key. Concatenation is denoted by ||.

C.1 KeyGen

  1. Generate s = Random32()
  2. Calculate (k,b) = H64(s, "eddsa_key_expansion")
  3. Calculate K = k G
  4. If the x coordinate of K is odd, negate both K and k.
  5. Return the private parameters (k,b) and the public key K.

The public key is encoded as the 255-bit y coordinate, in little endian byte order.

C.2 Sign

The Sign function takes as input the private parameters (k,b) and data to be signed.

  1. Calculate K = k G
  2. Set i = 0
  3. Calculate r = H64(b, "eddsa_nonce" || i || data) mod ℓ
  4. Calculate R = r G
  5. Calculate e = H16("eddsa_challenge" || R || K || data)
  6. Calculate s = (r - e*k) mod ℓ
  7. If s >= 2252, increment i and go back to step 3.
  8. Return the signature (s,e)

C.2.1 Signature serialization

The size of s is 252 bits and the size of e is 128 bits. The total size of the signature is 380 bits. s is serialized first in little-endian byte order (32 bytes). Then e is appended (16 bytes). Finally, the least significant 4 bits of e[15] are copied to the most significant 4 bits of s[31] (these bits are always zero because s < 2252). When the signature is serialized in base32, the redundant least significant 4 bits of e[15] are omitted, so the signature fits exactly into 76 characters.

C.3 Verify

The Verify function accepts a signature (s,e), a public key K and data that was signed.

  1. Calculate R = s G + e K
  2. Calculate e' = H16("eddsa_challenge" || R || K || data)
  3. Check that e ?= e'
@tevador
Copy link
Author

tevador commented Jan 1, 2023

I think with a maximum length of 994 characters

This is the limit for one invoice. Multiple invoices can exceed it (each invoice has its own checksum). However, the practical limit for QR codes is not much higher than that anyways.

I'm not opposed to the removal of the multi-invoice feature if it's deemed unnecessary.

I ask myself whether we couldn't simply take a single Base32 character for it. Because I would be very surprised if this even reaches version 3 sometime in the future, thus 30 possible versions look pretty safe.

For versions 1-16, the posvarint is also a single character. I don't see any downsides to this solution. Btw, the CryptoNote block version is also a varint and having > 255 versions might seems like an overkill to someone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment