Skip to content

Instantly share code, notes, and snippets.

@tevador
Last active June 7, 2026 10:39
Show Gist options
  • Select an option

  • Save tevador/639d083c994c1ef9401832c08e2b7832 to your computer and use it in GitHub Desktop.

Select an option

Save tevador/639d083c994c1ef9401832c08e2b7832 to your computer and use it in GitHub Desktop.
Jamtis

Jamtis [draft]

Jamtis is a new addressing protocol for Monero. The protocol introduces a new address format, with 400-character addresses, and has several new features. Most importantly, the new format allows for post-quantum forward secret transactions that can't be decrypted even if the address is publicly known and the elliptic curve discrete logarithm (ECDLP) is broken.

Additionally, the new scheme allows users to delegate blockchain scanning to a 3rd party service without revealing which specific outputs belong to the wallet or the amounts that were received. New addresses can be created statelessly (without the need to keep track of how many addresses have been generated) and balance recovery doesn't need a precomputed table.

Jamtis is backward compatible with the Carrot transaction protocol, requiring only 132 additional bytes in tx-extra per transaction. Jamtis addresses can coexist with CryptoNote addresses and the resulting transactions are indistinguishable in the blockchain.

Table of Contents

1. Introduction

1.1 Why a new address format?

When Monero was created in 2014, it inherited the CryptoNote addressing scheme [1]. Originally, each wallet only had a single public address and payments were disambiguated with payment IDs. In 2017, subaddresses were introduced, which allowed each wallet to generate a virtually unlimited number of seemingly unlinkable addresses.

In 2019, a weakness of subaddresses was identified, which allows an attacker to link two subaddresses belonging to the same wallet. This is called the "Janus attack" [2].

In 2026, Monero will upgrade to a new transaction protocol called Carrot [3], which provides mitigations for the Janus attack and offers address-conditional forward privacy (i.e. forward privacy if addresses are kept secret).

However, several issues with the legacy addressing scheme remain unresolved:

  1. Wallets with publicly known addresses lose nearly all privacy against a quantum-enabled adversary.
  2. Wallets that use a third party service for scanning the blockchain lose nearly all privacy.
  3. Generating subaddresses requires keeping track of a global counter, which complicates implementations and may cause merchants to prefer legacy integrated addresses [4].
  4. The detection of outputs received to subaddresses is based on a lookup table, which can sometimes cause the wallet to miss outputs [5].
  5. Checking two addresses for equality is difficult for humans because CryptoNote addresses are long and case-sensitive.

1.2 Jamtis

The goal of Jamtis is to tackle the shortcomings of CryptoNote addresses that were mentioned above. Specifically:

  1. Jamtis wallets with publicly known addresses retain a certain level of privacy even against a quantum-enabled adversary.
  2. Jamtis wallets using a third-party scanning service retain a certain level of privacy.
  3. Jamtis addresses can be safely generated without keeping track of a global counter.
  4. Balance recovery for Jamtis wallets can be done reliably without the need to use a precomputed table of keys.
  5. Jamtis addresses can be quickly compared thanks to a "visual prefix" consisting of 30 lowercase characters.

Jamtis focuses on post-quantum privacy because all past and present Monero transactions are vulnerable to quantum privacy-breaking attacks due to the "harvest now, decrypt later" strategy.

Additional goals are:

  1. Backward compatiblity with Carrot without hard forking changes.
  2. Enotes sent to Jamtis addresses are indistinguishable from enotes sent to legacy addresses.
  3. Jamtis addresses retain existing security properties of Carrot, especially Janus attack protection.

Jamtis also comes with a new 16-word mnemonic scheme called Polyseed [6] that will replace the legacy 25-word seed for new wallets.

1.3 Non-goals

An explicit non-goal of Jamtis is post-quantum soundness. This includes preventing a quantum-enabled adversary from:

  1. opening Pedersen commitments to arbitrary monetary values
  2. forging spend authorization proofs and linking tags
  3. forging membership proofs

Past and present Monero transactions are safe from soundness-breaking quantum attacks, assuming no cryptographically relevant quantum computers exist at this moment. Both Carrot and Jamtis support a migration protocol that will be used in a future fully post-quantum upgrade.

2. Features

2.1 Address format

Jamtis addresses start with the prefix xmr1 and consist of 400 alphanumeric characters. An example of an address: xmr1msfrcj9q4su57d8gujk1y0dhyiLwP8jCd6PC79rF1eg0NqdZ6pV0VyvbS3O9Lj2U3EcAUIkbTWQ2vjtTOslrvwWDpYKOmwo6N0IiPyO895hidBr9Q8CTrf8sFlkpIGkniA9jhh8KiDToxJhl07xPPsaegIhyyAfWstZXTLvmHjJ0EgJl1EUjDnCvNPziVOc3kSeW3fPBCxCAESvtRyunxjmID6BBzBWrG11qg0bRw9g3pNjQK4qy0wvyo3EJMJT01owyZeXAUcPHUCb0xTTY3OxMulHRK94RSJ8izidgneG9ibpqtxJiclsWoUA2F27V0WloLn7ToKd6mKPSM8J4Ld4z0RFXYc6Oyt91eEMxwbxzbIT6ddyNWNc7P3MtzHEBYDxL3w8KZubX

2.1.1 Visual prefix

The first 30 characters of a Jamtis address are always lowercase and form a "visual prefix" which encodes a cryptographic hash of the whole address and can be used to compare Jamtis addresses for equality much more easily than CryptoNote addresses. This partially mitigates the length of the address as wallet software can display just the first 30 characters.

2.2 Light wallet scanning

Jamtis introduces new wallet tiers below the view-only wallet. One of the new wallet tiers called "FilterAssist" is intended for wallet-scanning and only has the ability to calculate a part of the Carrot view tag [7].

View tags can be used to eliminate the majority of outputs that don't belong to the wallet. Possible use cases include:

2.2.1 Wallet component

A wallet can have a "FilterAssist" component that stays connected to the network at all times and filters out outputs in the blockchain. The full wallet can thus be synchronized much faster when it comes online (it only needs to check outputs with a matching view tag).

2.2.2 Third party services

If the "FilterAssist" private key is provided to a third party, it can preprocess the blockchain and provide a list of potential transactions. This reduces the amount of data that a light wallet has to download. The third party will not learn which outputs actually belong to the wallet and will not see output amounts.

2.3 Wallet tiers for merchants

Jamtis introduces new wallet tiers that are useful for merchants.

2.3.1 Address generator

This tier is intended for merchant point-of-sale terminals. It can generate addresses on demand, but otherwise has no access to the wallet (i.e. it cannot recognize any payments in the blockchain).

2.3.2 Payment validator

This wallet tier combines the Address generator tier with the ability to also view received payments (including amounts). It is intended for validating paid orders. It cannot see outgoing payments and received change.

2.4 Full view-only wallets

Jamtis supports full view-only wallets that can identify spent outputs (unlike legacy view-only wallets), so they can display the correct wallet balance and list all incoming and outgoing transactions.

2.5 Robust output detection

Jamtis addresses and outputs contain an encrypted address tag which enables a more robust output detection mechanism that does not need a lookup table and can reliably detect outputs sent to arbitrary wallet addresses.

3. Notation

3.1 Miscellaneous definitions

  1. The function BytesToInt(x) deserializes a little-endian integer from a byte string x.
  2. The function IntToBytes(i) converts an integer i to a little-endian byte string.
  3. The function RandBytes(b) generates a random b-byte string.
  4. Concatenation is denoted by ||.

3.2 Hash function

The function Hb(x) with parameters b, x, refers to the Blake2b hash function [8] initialized as follows:

  • The output length is set to b bytes.
  • Hashing is done in sequential mode.
  • The Personalization string is set to the ASCII value "Monero", padded with zero bytes.
  • The input x is hashed.

The function SecretDerive is defined as:

SecretDerive(x) = H32(x)

3.3 General public key cryptography

Monero uses Ed25519, a twisted Edwards elliptic curve [9], for most of its public key operations such as commitments and digital signatures. The following three generator points are used:

Point Serialized (hex)
G 5866666666666666666666666666666666666666666666666666666666666666
H 8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94
T 61b736ce93b62a3d3778ab204da85d3b4cdc07250f5da7e3df2629928134d526

Private keys for Ed25519 are 32-byte integers denoted by a lowercase letter k. They are generated using the following function:

KeyDerive1(x) = BytesToInt(H64(x)) mod ℓ

where ℓ = 2252 + 27742317777372353535851937790883648493 is the size of the prime order subgroup.

Public keys (elements of the prime-order subgroup) are denoted by the capital letter K (for keys) or C (for commitments) and are serialized as 256-bit integers, with the lower 255 bits being the y-coordinate of the corresponding Ed25519 point and the most significant bit being the parity of the x-coordinate.

The group operation is denoted additively. Scalar multiplication is denoted by a space, e.g. K = k G.

We define the function EdShortSign(k, x), which produces a 48-byte short Schnorr signature [10] of x using the private key k. The corresponding verification function is EdShortVerify(sig, K, x), which takes a signature sig, public key K and the signed data x.

3.4 Classical public key encryption

For classical public key encryption, Jamtis uses a variant of X25519, which is an elliptic curve Diffie-Hellman (ECDH) key exchange over Curve25519 [11].

Only a single generator point B is used with x = 9.

Private keys for Curve25519 are 32-byte integers denoted by a lowercase letter d. They are constructed using the following KeyClamp2(i) function from a uniformly distributed 32-byte integer i:

  1. i[31] &= 0x7f (clear the most significant bit)
  2. i[0] &= 0xf8 (clear the least significant 3 bits)
  3. return i

Note that some X25519 implementations also clamp bit 254 to 1, but this is not done in Jamtis due to the need to support inverted keys.

Private keys are derived deterministically using the following KeyDerive2(x) function:

  1. i = BytesToInt(H32(x))
  2. return KeyClamp2(i)

The KeyClamp2 function causes all Curve25519 private keys to be multiples of the cofactor 8, which ensures that all public keys are in the prime-order subgroup. The multiplicative inverse modulo is calculated as 1/d = 8*(8*d)-1 to preserve the aforementioned property.

Public keys are denoted by the capital letter D and are serialized as the x-coordinate of the corresponding Curve25519 point. Scalar multiplication is denoted by a space, e.g. D = d B and is done using the Montgomery ladder.

Additionally, we define the function PublicToEdwards(D), which takes a Curve25519 public key D and outputs the corresponding Ed25519 public key K with an even-valued x coordinate. The corresponding function PrivateToEdwards(d) converts the Curve25519 private key d to the Ed25519 private key k such that k G = PublicToEdwards(d B).

3.5 Post-quantum public key encryption

For post-quantum public key encryption, Jamtis uses CSIDH-1024 [12], which is a cryptosystem based on supersingular isogenies and allows for public key operations that resemble a classical Diffie-Hellman key exchange. Appendix A explains why CSIDH was chosen over other post-quantum algorithms.

All CSIDH calculations are done in the finite field $F_p$ with $p =$ 0xece55ed427012a9d89dec879007ebd7216c22bc86f21a080683cf25db31ad5bf06de2471cf9386e4d6c594a8ad82d2df811d9c419ec83297611ad4f90441c800978dbeed90a2b58b97c56d1de81ede56b317c5431541f40642aca4d5a313709c2cab6a0e287f1bd514ba72cb8d89fd3a1d81eebbc3d344ddbe34c5460e36453, a 1020-bit prime.

Valid CSIDH public keys are supersingular elliptic curves $y^2 = x^3 + Zx^2 + x$ with $Z, x, y \in F_p$. CSIDH public keys are denoted with the letter Z and are equal to the value of the curve coefficient. The starting curve is $E: y^2 = x^3 + x$.

A CSIDH private key is denoted with a lowercase letter z and represents a secret isogeny between two elliptic curves. Jamtis uses the private key structure from CTIDH [13] because it offers the fastest group action calculation. For these purposes, the 130 odd primes that divide $p+1$ are split into 23 batches of sizes 2, 3, 5, 4, 6, 6, 6, 6, 6, 7, 7, 7, 6, 7, 7, 5, 6, 5, 10, 3, 10, 5 and 1. Private keys are then generated with the batch bounds of 2, 4, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 3, 6, 2, 6, 2 and 0 (111 isogenies). This allows for approximately 2256 distinct keys. CSIDH private keys are generated deterministically using the function KeyDerive3 and non-deterministically using the function KeyGen3.

The isogeny action is denoted with *, for example Z = z * E, where E is the base curve.

3.6 Block cipher

The function BlockEnc(s, x) refers to the application of the Twofish [14] permutation using the secret key s on the 16-byte input x. The function BlockDec(s, x) refers to the application of the inverse permutation using the key s.

3.7 Authenticated encryption

We define the pair of functions AeadEnc(s, plaintext, ad) and AeadDec(s, ciphertext, ad), which encrypt/decrypt plaintext/ciphertext with associated data ad using the 32-byte secret key s. The authentication tag size is 16 bytes. The algorithm used is ChaCha20-Poly1305 [15].

3.8 Binary-to-text encoding

3.8.1 General encoding rules

Binary-to-text encodings in general convert a byte string b to a character string s of length n using a specified alphabet. The encoding process is done by interpreting b as a little-endian integer i and then repeatedly dividing by the length of the alphabet and appending the character corresponding to the remainder until the length of s is equal to n. In pseudocode:

def encode_base(b, n, alphabet):
   i = BytesToInt(b)
   s = ''
   while len(s) < n:
      r = i % len(alphabet)
      i /= len(alphabet)
      c += alphabet[r]
   return s

The original byte string b can be decoded from s by reversing the process.

3.8.2 Base32 encoding

Base32 in this specification uses the alphabet xmrbase32cdfghijknpqtuwy01456789. This alphabet was selected for the following reasons:

  1. The order of the characters has a unique prefix that distinguishes the encoding from other variants of "base32".
  2. The alphabet contains all digits 0-9, which allows numeric values to be encoded in a human-readable form.
  3. Excludes the letters o, l, v and z for the same reasons as the z-base-32 encoding [16].

Base32 is used to encode the visual prefix.

3.8.3 Base62 encoding

Base62 in this specification uses the alphabet XmrBase62bcdfghijklnopqtuvwxyzACDEFGHIJKLMNOPQRSTUVWYZ01345789. This alphabet contains all alphanumeric ASCII characters with a unique prefix. Base62 is used to encode the address payload and was selected for maximum encoding density where human-readability is not required.

4. Wallets

4.1 Wallet parameters

Each Jamtis wallet consists of two main keys, a timestamp and a bit flag:

Field Type Description
kps private key prove-spend key
svb secret key view-balance secret
birthday timestamp date when the wallet was created
jamtis bit always set to 1

The prove-spend key kps is required to spend money in the wallet and the view-balance secret svb provides full view-only access.

The birthday timestamp is important when restoring a wallet and determines the blockchain height where scanning for owned outputs should begin.

Wallets with the jamtis bit set to 0 are legacy wallets using the CryptoNote address format and the legacy keys. Jamtis wallets always set the jamtis bit-flag to 1 and use the new address format.

4.2 Wallet creation

4.2.1 Standard wallets

Standard Jamtis wallets are generated as a 16-word Polyseed mnemonic [6], which provides the wallet master secret sm and also encodes the date when the wallet was created and the jamtis bit-flag. The keys kps and svb are derived from the master secret.

Field Derivation
sm from Polyseed
kps kps = KeyDerive1("Jamtis prove spend key" || sm)
svb svb = SecretDerive("Jamtis view balance secret" || sm)
birthday from Polyseed
jamtis from Polyseed

4.2.2 Multisignature wallets

Multisignature wallets are generated in a setup ceremony, where all the signers collectively generate the prove-spend key kps and the view-balance secret svb.

Field Derivation
kps setup ceremony
svb setup ceremony
birthday setup ceremony
jamtis setup ceremony

4.3 Additional keys

There are additional keys derived from svb:

Key Name Derivation Used to
sgp generate-image preimage secret sgp = SecretDerive("Jamtis generate-image preimage secret" || svb) preimage for the generate-image key
kgi generate-image key kgi = KeyDerive1("Carrot generate-image key" || sgp || kps T) generate key images
svr view-received secret svr = SecretDerive("Jamtis view received secret" || svb) find and decode received (external) enotes
dur unlock-received classical key dur = KeyDerive2("Jamtis unlock received classical key" || svr) derive enote shared secrets
zur unlock-received post-quantum key zur = KeyDerive3("Jamtis unlock-received post-quantum key" || svr) derive enote shared secrets
dir identify-received classical key dir = KeyDerive2("Jamtis identify-received key" || svr) derive enote shared secrets
sfa filter-assist secret sfa = SecretDerive("Jamtis filter-assist secret" || svb) calculate primary view tags (internal)
dfa filter-assist key dfa = KeyDerive2("Jamtis filter-assist key" || sfa) calculate primary view tags (external)
sga generate-address secret sga = SecretDerive("Jamtis generate-address secret" || svb) generate addresses
sct cipher-tag secret sct = SecretDerive("Jamtis cipher-tag secret" || sga) encrypt/decrypt address tags

The key kgi is required to generate key images.

The secret svr (and its child keys) provides the ability to calculate the sender-receiver shared secrets when scanning for received (external) payments. The key dfa and the secret sfa can recognize candidates for owned enotes by matching the primary view tag.

The secret sga (and its child secret sct) is used to generate public addresses.

4.4 Key hierarchy

The following figure shows the overall hierarchy of wallet keys. Note that the master secret sm doesn't exist for multisignature wallets.

s_m (master secret)
 |
 |
 |
 +- k_ps (prove-spend key)
 |
 |
 |
 +- s_vb (view-balance secret)
     |
     |
     |
     +- s_gp (generate-image preimage secret)
     |   |
     |   |
     |   | 
     |   +- k_gi (generate-image key)
     |
     |
     |
     +- s_vr (view-received secret)
         |
         |
         |
         +- d_ur (unlock-received classical key)
         |
         |
         |
         +- z_ur (unlock-received post-quantum key)
         |
         |
         |
         +- d_ir (identify-received key)
         |
         |
         |
         +- s_fa (filter-assist secret)
         |   |
         |   |
         |   |
         |   +- d_fa (filter-assist key)
         |
         |
         |
         +- s_ga (generate-address secret)
             |
             |
             |
             +- s_ct (cipher-tag secret)

4.5 Wallet public keys

There are 5 global wallet public keys. These keys are not usually published, but are needed by lower wallet tiers.

Key Name Value
Ks spend key Ks = kgi G + kps T
Dur unlock-received classical key Dur = dur B
Zur unlock-received post-quantum key Zur = zur * E
Dir identify-received key Dir = dir Dur
Dfa filter-assist key Dfa = dfa Dur

4.6 Wallet access tiers

The private key hierarchy enables the following useful wallet tiers:

Tier Secret Public keys Off-chain capabilities On-chain capabilities
AddrGen sga Ks, Dur, Zur, Dir, Dfa generate public addresses none
FilterAssist sfa - recognize all public wallet addresses eliminate the majority of non-owned outputs
ViewReceived svr Ks interactive payments view received payments
ViewAll svb Ks all view all
Master sm - all all

4.6.1 Address generator (AddrGen)

This wallet tier can generate public addresses for the wallet. It doesn't provide any blockchain access.

4.6.2 Output scanning wallet (FilterAssist)

Thanks to view tags, this tier can eliminate the majority of outputs that don't belong to the wallet. This tier should provide a noticeable UX improvement with a limited impact on privacy. Possible use cases include:

  1. An always-online wallet component that filters out outputs in the blockchain. A higher-tier wallet can thus be synchronized much faster when it comes online.
  2. Third party scanning services. The service can preprocess the blockchain and provide a list of potential outputs. This reduces the amount of data that a light wallet has to download.

4.6.3 Payment validator (ViewReceived)

This level provides the wallet with the ability to accept and verify incoming payments, but cannot see any outgoing payments and change enotes. It can be used for payment processing or auditing purposes.

4.6.4 View-only wallet (ViewAll)

This is a full view-only wallet that can see all incoming and outgoing payments (and thus can calculate the correct wallet balance).

4.6.5 Master wallet (Master)

This tier has full control of the wallet.

5. Addresses

5.1 Address generation

Jamtis wallets can generate up to 2128 different addresses. Each address is constructed from a 128-bit index j. The size of the index space allows stateless generation of new addresses without collisions, for example by constructing j as a UUID [17].

Each Jamtis address encodes the tuple (tj, K1j, D2j, D3j, D4j, Z5j, Z6j), where tj = (j', vtaj) is the address tag and the other five values are public keys. The address tag consists of the encrypted value of j and a 24-bit address view tag vtaj.

5.1.1 Address keys

The five public keys are constructed as:

  • K1j = kabj Ks
  • D2j = (1 / dabj) Dur
  • D3j = (1 / dabj) Dfa
  • D4j = (1 / dabj) Dir
  • Z5j = zabj * Zur

The address blinding private keys kabj, dabj and zabj are derived as follows:

  • sap1j = SecretDerive("Jamtis address index preimage 1" || sga || j)
  • sap2j = SecretDerive("Jamtis address index preimage 2" || sap1 || j || Ks || Dur || Dfa || Dir || Zur)
  • kabj = KeyDerive1("Carrot subaddress scalar" || sap2j || Ks)
  • dabj = KeyDerive2("Jamtis address classical blinder" || sap2j || Dur || Dfa || Dir)
  • zabj = KeyDerive3("Jamtis address post-quantum blinder" || sap2j || Zur)

The address index preimage sap1j can be used to prove that the address was constructed from the index j and the public keys Ks, Dur, Dfa, Dir, Zur without revealing sga.

The address index preimage sap2j can be used to prove that the individual address public keys were constructed from the corresponding wallet public keys without revealing the address index.

5.1.2 Address tag

The 19-byte address tag tj = (j', vtaj) is constructed as follows:

  • j' = BlockEnc(sct, j)
  • vtaj = H3("Jamtis address view tag" || sga || j')

5.2 Address encoding

5.2.1 Address structure

An address has the following overall structure:

Field Length Description
Header 5 human-readable address header (§ 5.2.2)
Address checksum 24 base32 encoded address checksum (§ 5.2.3)
Prefix checksum 1 base32 encoded prefix checksum (§ 5.2.4)
Payload 370 base62 encoded address tuple (§ 5.2.4)

The length is given in characters. The total address length is 400. It is recommended that wallet software displays the first 30 characters of each address (the header, address checksum and the prefix checksum, which together form the "visual prefix").

5.2.2 Address header

The address starts with a human-readable header, which has the following format consisting of 5 alphanumeric characters:

"xmr" [version char] [network type char]

The software decoding an address shall abort if the first 3 bytes are not 0x78 0x6d 0x72 ("xmr").

The "xmr" prefix serves as a disambiguation from legacy addresses that start with "4" or "8". Additionally, base58 strings that start with the character x are invalid due to overflow [18], so legacy Monero software can never accidentally decode a Jamtis address.

The version character is "1". The software decoding an address shall abort if a different character is encountered.

The following 3 network types are defined:

network char network type
"t" testnet
"s" stagenet
"m" mainnet

The software decoding an address shall abort if an invalid network character is encountered.

5.2.3 Address checksum

The human-readable header is followed by a 24-character address checksum. The purpose of the checksum is to detect both accidental and malicious corruption of the address and it also helps to visually compare two addresses for equality.

The 120-bit address checksum is calculated as follows:

checksum = H15("Jamtis address checksum" || [version char] || [network type char] || tj || K1j || D2j || D3j || D4j || Z5j)

The checksum provides 120 bits of preimage resistance, so it can be used to compare two addresses for equality in a cryptographically secure way if one of the addresses comes from a trusted source (e.g. the user's own wallet).

It should be noted that a malicious actor can create two distinct addresses with the same checksum after about 260 attempts, so it's generally not safe to compare just the checksum in the case of two addresses coming from untrusted sources.

5.2.4 Prefix checksum

Because the visual prefix can be used separately from the whole address (e.g. for display or verification purposes), it includes a simple but powerful checksum, which is calculated from the preceding 29 characters by interpreting them as the coefficients of a polynomial over GF(32). The full prefix checksum algorithm is in Appendix D.

5.2.5 Payload

The address tuple (tj, K1j, D2j, D3j, D4j, Z5j), a total of 275 bytes, is encoded in 370 base62 characters.

6. Addressing protocol

The Jamtis addressing protocol is backward compatible with the Carrot addressing protocol. The following table lists the relevant transaction fields and how they are used in each protocol:

Location Field Carrot Jamtis
enote output public key Ko Ko
enote view tag vt vt1, vt2
enote amount commitment Ca Ca
enote encrypted Janus anchor anchorenc j'enc
enote encrypted amount aenc aenc
transaction tx extra De, pidenc De, pidenc, tag_size, Ze

6.1 Enote format

A Jamtis enote consists of the output public key Ko, the primary view tag vt1, the secondary view tag vt2, the amount commitment Ca, encrypted address tag j'enc, and the encrypted amount aenc.

6.1.1 The output key

The output key is constructed as Ko = K1 + kgo G + kto T, where kgo and kto are key extensions of the address spend key K1.

6.1.2 View tags

While Carrot uses a monolithic 24-bit view tag, Jamtis split the field into two parts. The first tag_size bits (in little endian order) form the primary view tag vt1 and the remaining bits are the secondary view tag vt2. Each view tag is derived from a different shared secret.

In the case of hidden enotes (§ 6.7.2), all 24 bits are used for vt2.

6.1.3 Amount commitment

The amount commitment is constructed as Ca = ka G + a H, where ka is the commitment mask and a is the amount.

6.1.4 Address tag

The address tag j' is encrypted by exclusive or (XOR) with an encryption mask mj'.

6.1.5 Amount

The amount a is encrypted by exclusive or (XOR) with an encryption mask ma.

6.2 Transaction global fields

Transaction global fields are stored in "tx extra".

6.2.1 Classical public keys

The classical ephemeral public keys De are stored the same way as in Carrot.

6.2.2 Payment ID

When all enotes of a transaction are sent to Jamtis addresses, the encrypted payment ID is set to a random value: pidenc = RandBytes(8). If at least one enote goes to a legacy address, the payment ID follows the Carrot rules.

When decrypting enotes, Jamtis wallets ignore the payment ID field.

6.2.3 Jamtis extra field

All transactions should include a new tx extra field with the following format:

0xa1 [encoded_size] [tag_size] Ze

The first byte 0xa1 is the Jamtis extra field identifier. The field encoded_size encodes the number of bytes that follow in varint128 format. The next byte tag_size specifies the size of the primary view tag used in the transaction. The permitted range is 0-24, but a value of 8 is enforced by a relay rule. The remainder of the field contains a single public key Ze (128 bytes). The total size of the Jamtis extra field is 132 bytes.

6.3 Enote types

There are 2 enotes types: payment and change.

6.3.1 Payment enotes

These enotes represent a received payment and show up in the wallet transaction history as a positive amount. These enotes can be received both from the outside or from the wallet itself (internal payments).

6.3.2 Change enotes

These enotes represent change returned back to the wallet after a payment is made. The UX difference is that the wallet will not display change enotes in the transaction history as a positive amount, but rather will use the change to reduce the amount that was spent. Change enotes always come from the wallet itself. If a change enote has a zero amount, it's called a dummy enote.

6.4 Input context

For each transaction, we assign a value input_context, whose purpose is to be unique for every single transaction within a valid ledger. We define this value as follows:

transaction type input_context
coinbase "C" || IntToBytes256(block height)
non-coinbase "R" || first spent key image

6.5 Sender-receiver shared secrets

When sending to a Jamtis address (tj, K1, D2, D3, D4, Z5), the sender first generates the ephemeral private keys:

  • de = KeyGen2()
  • ze = KeyGen3()

The ephemeral public keys included in the transaction are De = de D2 and Ze = ze * E.

The sender and the recipient can then both derive the following four shared secrets:

Shared secret Sender Recipient
ssr1 de D3 dfa De
ssr2 de D4 dir De
ssr3 de B (dabj / dur) De
ssr4 ze * Z5 zabj * zur * Ze

The four shared keys are used to derive a high-level shared secret:

ssrctx = SecretDerive("Jamtis sender-receiver secret" || ssr1 || ssr2 || ssr3 || ssr4 || De || Ze || input_context)

6.6 Janus outputs

In case of a Janus attack, the recipient will derive different values of the shared secrets ssr3 and ssr4, which depend on the address index j. The attacker will not be able to derive the recipient's values even if they know the values of dabj and zabj for one of the involved addresses.

6.7 Internal enotes

Enotes which go to an address that belongs to the sending wallet are called "internal enotes". The most common type are change enotes, but internal payments are also possible. Coinbase transactions are not considered to be internal.

For internal enotes, a different construction of the shared secrets is used:

Shared secret change payment
ssr1 sfa sfa
ssr2 svb svr
ssr3 svb svr
ssr4 svb svr

This ensures that:

  1. The FilterAssist wallet tier can still calculate the primary view tag with the knowledge of sfa.
  2. Change enote encryption/decryption can only be done by the ViewAll wallet tier.
  3. Internal payments can be encrypted/decrypted by the ViewReceived wallet tier, which is important for interactive payments.

6.7.1 Mandatory change

Every transaction that spends funds from the wallet must produce at least one internal enote, typically a change enote. If there is no change left, a dummy enote is added (change with a zero amount). This ensures that all transactions relevant to the wallet have a matching primary view tag on at least one output.

In a 2-out transaction with two internal enotes, one enote's enote_type must be "payment", and the other "change".

6.7.2 Hidden enotes

If a transaction produces more than one internal enote (e.g. a payment enote and a change enote or two change enotes), only one of them gets a primary view tag. For the remaining internal enotes, all 24 bits are filled with the secondary view tag. This prevents the FilterAssist wallet tier from linking transactions to the wallet based on the number of primary view tag matches within a transaction.

The consequence of this rule is that a wallet scanning for incoming transactions has to scan all enotes of transactions with at least one matching primary view tag. However, most of them will only have to be tested for a secondary view tag match.

6.8 Enote derivations

6.8.1 Intermediate values

The intermediate enote components are derived as follows:

Symbol Name Derivation
ka amount commitment blinding factor ka = ScalarDerive("Carrot commitment mask" || ssrctx || a || K1j || enote_type)
kgo output pubkey extension G [coinbase] kgo = ScalarDerive("Carrot coinbase extension G" || ssrctx || a || K1j)
kto output pubkey extension T [coinbase] kto = ScalarDerive("Carrot coinbase extension T" || ssrctx || a || K1j)
kgo output pubkey extension G [non-coinbase] kgo = ScalarDerive("Carrot key extension G" || ssrctx || Ca)
kto output pubkey extension T [non-coinbase] kto = ScalarDerive("Carrot key extension T" || ssrctx || Ca)
mj' encryption mask for j' mj' = SecretDerive("Jamtis encryption mask j'" || ssr2 || Ko)[:16]
mvt2 encryption mask for vt2 mvt2 = SecretDerive("Jamtis encryption mask vt2" || ssr2 || input_context || Ko)[:3]
ma encryption mask for a ma = SecretDerive("Carrot encryption mask a" || ssrctx || Ko)[:8]

The variable enote_type is "payment" or "change" depending on the human-meaningful tag that a sender wants to express to the recipient. For all external enotes, enote_type must be equal to "payment". For internal enotes, enote_type has to match the secret key used to derive ssrctx.

Notice the difference in the derivation of the output pubkey extensions kgo, kto between coinbase enotes and non-coinbase enotes. The reason for this difference is twofold. First, this allows the receiver of a coinbase enote to prove that its output pubkey binds to the account spend pubkey. Second, binding the coinbase extensions to the amount instead of the amount commitments saves a scalar-point multiplication and point addition.

6.8.2 Component derivations

Symbol Name Derivation
Ko output pubkey Ko = K1j + kgo G + kto T
Ca amount commitment Ca = ka G + a H
aenc encrypted amount aenc = IntToBytes64(a) ⊕ ma
vt1 primary view tag vt1 = SecretDerive("Jamtis primary view tag" || ssr1 || input_context || Ko)
vt2 secondary view tag vt2 = vtaj ⊕ mvt2
j'enc encrypted address tag j'enc = j' ⊕ mj'

The primary view tag binds to input_context so that an external observer (i.e. someone without knowledge of at least ssr1) cannot simply copy De, Ko, and vt1 into a new transaction to force a view tag match. Binding to input_context causes the view tag of a copied enote to match with the same probability as any random, unrelated enote.

The secondary view tag is constructed by encrypting the address view tag, which makes it harder for an ECDLP-breaking adversary, who can derive ssr2 of external enotes, to decide if an enote has a matching secondary view tag without the knowledge of the corresponding address.

7. Security properties

TODO

Credits

Special thanks to everyone who commented and provided feedback on the original Jamtis, Jamtis-RCT, in #monero-research-lab or anywhere else. Some of the ideas were incorporated in this document.

Special thanks to @jeffro256 for the Carrot speficiation. Some Carrot improvements have been backported to Jamtis.

References

  1. CryptoNote: https://github.com/monero-project/research-lab/blob/master/whitepaper/whitepaper.pdf
  2. Janus attack: https://www.getmonero.org/2019/10/18/subaddress-janus.html
  3. Carrot: https://github.com/jeffro256/carrot/blob/master/carrot.md
  4. Merchants and subaddresses: monero-project/meta#299 (comment)
  5. Subaddress lookahead issue: monero-project/monero#8138
  6. Polyseed: https://github.com/tevador/polyseed
  7. Carrot view tags: https://github.com/jeffro256/carrot/blob/master/carrot.md#734-view-tags
  8. Blake2: https://eprint.iacr.org/2013/322.pdf
  9. Ed25519: https://ed25519.cr.yp.to/ed25519-20110926.pdf
  10. Short Schnorr: https://eprint.iacr.org/2019/1105
  11. Curve25519: https://cr.yp.to/ecdh/curve25519-20060209.pdf
  12. CSIDH: https://eprint.iacr.org/2018/383
  13. CTIDH: https://eprint.iacr.org/2021/633
  14. Twofish: https://www.schneier.com/wp-content/uploads/2016/02/paper-twofish-paper.pdf
  15. ChaCha20-Poly1305: https://www.rfc-editor.org/info/rfc8439/
  16. Z-base32: http://philzimmermann.com/docs/human-oriented-base-32-encoding.txt
  17. UUID: https://en.wikipedia.org/wiki/Universally_unique_identifier
  18. Base58 overflow: https://github.com/monero-project/monero/blob/319b831e65437f1c8e5ff4b4cb9be03f091f6fc6/src/common/base58.cpp#L157
  19. McEliece: https://ipnpr.jpl.nasa.gov/progress_report2/42-44/44N.PDF
  20. Classic McEliece Round 4: https://classic.mceliece.org/nist.html
  21. HQC: https://pqc-hqc.org/doc/hqc_specifications_2025_08_22.pdf
  22. CRYSTALS-Kyber: https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf
  23. NTRU: https://csrc.nist.gov/CSRC/media/Projects/post-quantum-cryptography/documents/round-3/submissions/NTRU-Round3.zip
  24. NTRU Prime: https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf
  25. Swoosh: https://eprint.iacr.org/2023/271
  26. SIKE: https://csrc.nist.gov/csrc/media/Projects/post-quantum-cryptography/documents/round-4/submissions/SIKE-spec.pdf
  27. SIKE Attack: https://eprint.iacr.org/2022/975
  28. CSIDH quantum attack: https://eprint.iacr.org/2019/725
  29. ECDLP quantum attack: https://arxiv.org/abs/2306.08585
  30. CSIDH quantum circuit: https://eprint.iacr.org/2018/1059

Appendix A: Selecting a post-quantum encryption scheme

A.1 Special properties

When talking about post-quantum (PQ) encryption algorithms in a blockchain setting, it is important to define two special properties:

We call a key agreement protocol non-interactive (NI) if Alice and Bob can produce a shared secret just from the knowledge of each other's public key. This property is beneficial for Monero because a transaction can have up to 16 outputs and using a non-interactive protocol means Alice only needs to attach one public key that can be used to encrypt all outputs.

We call a key agreement protocol rerandomizable (RR) if Charlie can take Bob's public key and produce a different random-looking public key. Charlie alone must not be able to decrypt messages sent to the rerandomized key, but Charlie and Bob can jointly do so. This property is beneficial for Monero because it allows Bob to delegate address generation to Charlie without giving Charlie the ability to decrypt transactions.

A.2 Hardness assumptions and protocols

The discrete logarithm problem (DLP) is not considered to be hard to solve for a quantum computer.

The following 3 problems are assumed to be hard and have been used to construct PQ key exchange protocols:

A.2.1 Error correcting codes

Decoding a randomly chosen linear error correcting code is assumed to be computationally hard. One of the first public key cryptosystems, published in 1978 by R.J. McEliece [19], is based on this assumption. This cryptosystem has withstood almost 50 years of cryptanalysis and is considered to be one of the most secure choices for PQ encryption.

A modern version of the McEliece cryptosystem was submitted to the NIST Post-Quantum Cryptography Standardization (PQCS) [20], where it advanced to the 4th round, but was not selected for standardization.

In 2025, NIST standardized HQC, which is also based on error correcting codes [21].

A.2.2 Lattices

Finding a short vector in a multidimensional lattice is assumed to be computationally hard. A large number of PQ cryptosystems are based on this assumption. This includes CRYSTALS-Kyber [22], which was standardized by NIST in 2022. Other notable cryptosystems are NTRU [23] and NTRU Prime [24], which also participated in the NIST PQCS and both advanced to the 3rd round.

The most compact lattice-based cryptosystems have public keys and ciphertext sizes of around 1 KB and offer high security and high speed. However, some of the proposed cryptosystems might be covered by patents (for example US9094189B2 and US9246675B2).

The only known lattice-based non-interactive key exchange is Swoosh, which was published in 2023 [25].

A.2.3 Isogenies

Finding an isogeny between two elliptic curves is assumed to be hard. A prominent cryptosystem based on this assumption was SIKE [26], which advanced to the 4th round of the NIST PQCS, but was completely broken by a classical attack in 2022 [27]. The attack on SIKE doesn't imply that the isogeny hardness assumption is wrong becaue SIKE implicitly used a much stronger ad-hoc assumption.

CSIDH (pronounced "sea-side") is another cryptosystem based on isogenies. It was published in 2018 [12], so it didn't participate in the NIST PQCS. The attack that broke SIKE doesn't apply to CSIDH. CSIDH uses a commutative action, so it's a rare example of a PQ non-interactive key exchange. All known classical attacks on CSIDH have exponential complexity, but a subexponential quantum attack is known [28], which somewhat reduces the security of CSIDH compared to the previously mentioned cryptosystems. CSIDH is also relatively slow compared to lattice-based cryptosystems.

A.4 Comparison of post-quantum algorithms

Table A.1 lists a selection of PQ key exchange algorithms in terms of their public key size (which directly impacts address sizes), the ciphertext sizes (which impact transaction sizes) and the number of CPU cycles (in thousands) required to calculate the shared secret ("decapsulation" for interactive algorithms). For non-interactive algorithms, ciphertext size equals the public key size.

Algorithm Type PQ NI RR pk size ct size performance (kcy)
Curve25519 discrete log 32 32 120
McEliece-3488 error correction 261 120 96 130
HQC-1 error correction 2 241 4 433 13 918
NTRU-509 lattices 699 699 40
Kyber-512 lattices 800 768 35
sntrup653 lattices 994 897 56
Swoosh lattices 221 184 221 184 10 613
CSIDH-1024 isogenies 128 128 511 190

Table A.1: Comparison of post-quantum algorithms with Curve25519

Classic McEliece has a low blockchain cost, but would result in unusably large addresses.

HQC was selected by NIST mainly as a backup algorithm in case lattice hardness assumptions fail. It is unsuitable for blockchain applications.

NTRU, Kyber and NTRU Prime have very similar characteristics. Their performance is excellent and the bandwidth costs are somewhat acceptable for blockchain applications, although address lengths would exceed 1000 characters and pruned transaction sizes would increase about 5x.

While Swoosh is technically non-interactive, its large public key size completely negates this advantage for blockchain applications.

CSIDH is the only post-quantum algorithm that comes close to Curve25519 in terms of special properties and size. However, it is considerably slower and its post-quantum security is somewhat below the other algorithms. Despite these shortcomings, we considered CSIDH to be the strongest candidate for the following reasons:

  1. A CSIDH-1024 public key only increases the address length from 228 characters to 400 characters, which is acceptable.
  2. Thanks to the non-interactivity of CSIDH, a 16-output transaction still only needs a single CSIDH public key, so the blockchain size impact is relatively low at about 130 bytes per transaction.
  3. Due to the ability to rerandomize keys, the encryption scheme can be secure even if the address generator wallet tier is compromised.

A.3 Applying PQ encryption to Jamtis

TODO

A.4 Post-quantum security of CSIDH

TODO

Appendix B: Interactive payment protocol

Jamtis supports an optional interactive payment protocol (IPP) that offers a higher level of post-quantum privacy because it doesn't expose a key exchange on the blockchain.

B.1 Overview

When Alice wants to send funds to Bob interactively, a 4-step process needs to be executed, which includes 3 messages (2 from Bob to Alice and 1 from Alice to Bob).

For example, using any chat communication channel, the interaction between Alice and Bob may look like this:

Bob: xmr1my3wra6q9uhk5i6dsyen0udw6xbHssJ1hA8kQ9RCGGaOu9Lr0xH67GjBrwxSv8ORNLAVzCG79LEuZIW2rGDAbfwVWbNXA6XKRIQ44oRVFl5AMGsSQa8b9Ajp5x6zGtaP2hKG9eGtv8jr44VnQwjXoeOhSq1q574qVQDZOzcjTQxZ3Yj4Sgj1uEP5R51bMGyAdCwpmu6w5nZfasUcPrvtHjbuyYqvppE22IUlapDYX6YFR1eI7m2qLYUFfnURo6JSdtgBbzA1NcNOgSeDQVljgTuzI83YT8zfMJ4Op65Hu9zDd6JiyLSqxzeQypsIaezJZgWDXQweDjgwfAMjmqUE3aHzA7doKyeMIwu6fzpDUPyNw5L9knSopQL1s4CKqhVv0cwfnUufRLlX
Alice: xmrofr1mWtRfkoAWYLalOSGJfAiGmf34mzLbquYuSLjtRidQo0oVR7PAaAZ2RFrayTZCTD4Wv1KySePTMS0xftomMygZD1GdDMKpj1x9Gx1pqY1JQNPsTd16SlV8dRLJcVecucaaJwnQkz646gTP4eiKKGSLVoQWDslTauSL3P5AT8ebjEhnzMcpXotVh4MS2Ieq4spt1MZYgIksGld00V8iDXgJ2I6QMEovXlBXPKKwObmfEjfY7mPVP908U6QqWsbspSrQnoOEzuuLiZ8sGbPFqstxZ9jAoQ5S8RuEeM6MnrFB4rT6oLGcprv9eQYZaVeNSHsD1ZNk4ecSVSwV3k2CH10gXTxn7mOkVfbcd9EIuvZEX
Bob: xmracc1mEloDTikmRWnfP84FMaM2cwT33OvtVLQmPESwvIAslpBfLoekKSIxx318mr3VQyHteH64JAjJ9wkxp7E56u8p8fECCH7SQVch4eppSoSxfBPIWFMfZNw5qEgjRznuNvqwDJoinBESRjZzTV58oU2YoLu9Ev4Sp5xYgDTaWWmJ2RbSo2YZwqDLBGqmybl6V6sdnrdrIYhgItk47aHAHm26nRvUKAINtC3d5wInS0RLNr1DtIMNQ2FnpKw0GBJkmY2uyGwUPp6uvSUqy9hGXGH8T8LZhbtnf2IG1CIXeJQyIDJyrvwj6O3Ayx0wi6pyFL4ClBmkCfuMzsNRqiwPaDhvVb6eOIlTlcNAGocqtOtJFIIb4lPXMETdqcy

Remark B1: each of the three IPP messages fits in 400 alphanumeric characters, which makes IPP usable over the Internet Relay Chat (IRC) protocol or other protocols with a message length limit around 500 characters.

After Alice receives Bob's second message, she will be immediately able to submit a Monero transaction paying Bob. Bob can easily recognize this transaction on the blockchain.

The interactive protocol is stateless because neither Alice nor Bob need to store any extra information between messages. This is important because the protocol may never complete if, for example, Bob fails to send his second message. The protocol handles such failures by simply ignoring unfinished sessions.

The 4 interactive steps are called REQUEST, OFFER, ACCEPT and SEND.

B.2 REQUEST (Bob)

In this step, Bob sends his Jamtis address to Alice. Optionally, Bob may also send the amount he expects Alice to pay. This step may be skipped if Alice already knows Bob's address and the amount to send.

Remark B2: The IPP only uses 2 fields from a Jamtis address: the tag t and the key D2, so Bob's message could in principle be shorter, but sending the whole address allows Alice to pay Bob non-interactively if the IPP cannot be completed for some reason.

B.3 OFFER (Alice)

In the OFFER step, Alice sends Bob the information he needs to construct his enote.

Using t and D2 extracted from Bob's Jamtis address and the amount a, Alice performs the following steps:

  1. Generate seed = RandBytes(16)
  2. Select the first key_image that will be spent in the transaction
  3. Let ze = KeyDerive3("Jamtis IPP z_e" || svr || seed || "R" || key_image)
  4. Let Ze = ze * E
  5. Let doffer = KeyDerive2("Jamtis IPP d_offer" || svr || seed || t || D2)
  6. Let Doffer = doffer B
  7. Let macoffer = H16("Jamtis IPP mac_offer" || svr || seed || t || D2 || a || tag_size || "R" || key_image)
  8. Let soffer = SecretDerive("Jamtis IPP s_offer" || t || doffer D2)
  9. Let ciphertextoffer = AeadEnc(soffer, seed || macoffer || a || key_image || tag_size || Ze, t || Doffer)

The payload of the OFFER message is the tuple (t, Doffer, ciphertextoffer), which is a total of 268 bytes.

The OFFER message is encoded as:

"xmrofr" [version char] [network type char] [payload]

where version char is 1 and network type char is defined in § 5.2.2. The payload is encoded with base62. The total encoded length of the OFFER message is 369 characters.

Remark B3: The key Ze is encrypted because it will appear on-chain. Encryption ensures that Eve cannot find the transaction even if she secretly observes the whole interaction between Alice and Bob.

Remark B4: The private key ze is only known to Alice, which allows her to send other (possibly non-interactive) payments in the same transaction.

B.4 ACCEPT (Bob)

In the ACCEPT step, Bob decrypts Alice's OFFER message, constructs his own enote and sends the data back to Alice together with a payment proof.

  1. Check that the address tag t is correct. This is done by rederiving the address view tag as defined in § 5.1.2.
  2. Decrypt j from j'
  3. Let daccept = (dur / dabj)
  4. Let soffer = SecretDerive("Jamtis IPP s_offer" || t || daccept Doffer)
  5. Let (seed, macoffer, a, key_image, tag_size, Ze) = AeadDec(soffer, ciphertextoffer, t || Doffer). Abort if the decryption fails.
  6. Let saccept = SecretDerive("Jamtis IPP s_accept" || t || daccept Doffer)
  7. Let de = KeyDerive2("Jamtis IPP d_e" || saccept || "R" || key_image || a)
  8. Let De = de B
  9. Derive ssrctx using the rules from § 6.5 and § 6.7 for internal enotes.
  10. Derive Ko, Ca, aenc, vt1, vt2, j'enc using the rules from § 6.8.
  11. Let sigaccept = EdShortSign(PrivateToEdwards(daccept), a || t || D2 || "R" || key_image || Ko || vt || Ca || j'enc || aenc || De || tag_size || Ze)
  12. Let ciphertextaccept = AeadEnc(saccept, macoffer || a || key_image || tag_size || Ko || ka || aenc || vt || j'enc || sigaccept, t || D2 || seed)

The payload of the ACCEPT message is the tuple (t, D2, seed, ciphertextaccept), which is a total of 279 bytes.

The ACCEPT message is encoded as:

"xmracc" [version char] [network type char] [payload]

where version char is 1 and network type char is defined in § 5.2.2. The payload is encoded with base62. The total encoded length of the ACCEPT message is 383 characters.

Remark B5: The signature sigaccept sent by Bob in this step serves as a payment proof for Alice. The proof is only valid if the signed data match an actual transaction on the blockchain.

B.5 SEND (Alice)

In the SEND step, Alice decrypts Bob's ACCEPT message and submits the final Monero transaction.

  1. Let doffer = KeyDerive2("Jamtis IPP d_offer" || svr || seed || t || D2)
  2. Let saccept = SecretDerive("Jamtis IPP s_accept" || t || doffer D2)
  3. Let (macoffer, a, key_image, tag_size, Ko, ka, aenc, vt, j'enc, sigaccept) = AeadDec(saccept, ciphertextaccept). Abort if the decryption fails.
  4. Check macoffer ?= H16("Jamtis IPP mac_offer" || svr || seed || t || D2 || a || tag_size || "R" || key_image)
  5. Check that key_image corresponds to an unspent enote in the wallet.
  6. Let Ca = ka G + a H
  7. Let de = KeyDerive2("Jamtis IPP d_e" || saccept || "R" || key_image || a)
  8. Let De = de B
  9. Let ze = KeyDerive3("Jamtis IPP z_e" || svr || seed || "R" || key_image)
  10. Let Ze = ze * E
  11. Check EdShortVerify(sigaccept, PublicToEdwards(D2), a || t || D2 || "R" || key_image || Ko || vt || Ca || j'enc || aenc || De || tag_size || Ze)
  12. Submit the transaction to the Monero network.

Appendix C: Instant sync protocol

Jamtis supports an optional instant sync protocol (ISP) which allows for "instant" balance recovery without the need to scan the blockchain. Unlike the IPP (Appendix B), ISP payments are non-interactive, provided that the setup phase has been done.

C.1 Setup phase

When Alice and Bob want to take advantage of the ISP, they need to exchange addresses, i.e. Alice needs to give her Jamtis address (tA, K1A, D2A, D3A, D4A, Z5A) to Bob and Bob needs to give his Jamtis address (tB, K1B, D2B, D3B, D4B, Z5B) to Alice. They also need to explicitly agree to use the instant sync protocol.

After exchanging their addresses, Alice and Bob can establish the following shared secret:

sISPA,B = (durA / dabjA,A) D2B = (durB / dabjB,B) D2A

C.2 Enote construction

When constructing an enote for Bob, Alice derives the ephemeral key as follows:

deA = KeyDerive2("Jamtis ISP d_e" || D2B || sISPA,B || n),

where sISPA,B is the previously established shared secret with Bob and n is the ordinal payment index from Alice to Bob (how many payments Alice has sent to Bob using this payment channel). In practice, Alice can always select the lowest n that produces a unique value of the ephemeral key on the blockchain, so tracking the value of n is not strictly necessary.

Similarly, when Bob wants to pay Alice, he will derive the ephemeral key as:

deB = KeyDerive2("Jamtis ISP d_e" || D2A || sISPA,B || n),

where n is again the number of payments Bob has already sent to Alice using this payment channel.

The enote construction otherwise exactly follows the rules from § 6.

C.3 Balance recovery

C.3.1 Instant sync

Bob will be able to derive the value of deA before Alice actually sends the n-th payment. The expected ephemeral key attached to the enote will be:

DeA = deA D2B,

so Bob doesn't need to scan the blockchain. Instead, he can index all enotes using their value of De and will be able to find Alice's payment instantly when it appears on the blockchain. Note that the ephemeral key De is already present in every Jamtis enote as per § 6.2.1. This protocol doesn't add any new on-chain data, it only changes the way De is derived.

Similar rules will apply to Bob's payments to Alice.

C.3.2 Restored wallet

If Bob restores his wallet from the seed, the wallet will not remember Alice's address or the value of sISPA,B. Nevertheless, Bob will be able to discover Alice's payments by scanning the blockchain as usual. Restoring the wallet will close the payment channel and disable instant sync until a new channel is established.

Appendix D: Address prefix checksum

# Jamtis address prefix checksum algorithm
# based on a simple Reed-Solomon code over GF(32)

CHARSET32 = "xmrbase32cdfghijknpqtuwy01456789"

gf32_mul2_table = [
    0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
    9, 11, 13, 15, 1, 3, 5, 7, 25, 27, 29, 31, 17, 19, 21, 23,
]

def gf32_poly_eval(digits):
    # Horner's method at x = 2
    result = digits[0];
    for digit in digits[1:]:
        assert result < 32
        result = gf32_mul2_table[result] ^ digit
    return result

def make_poly(prefix):
    poly = []
    for c in prefix:
        poly.append(CHARSET32.index(c))
    return poly

addr_prefix_test = "xmr1msfrcj9q4su57d8gujk1y0dhy"

poly = make_poly(addr_prefix_test)
poly.append(0)
check_digit = gf32_poly_eval(poly)
check_char = CHARSET32[check_digit]
addr_prefix = addr_prefix_test + check_char
print(addr_prefix)
poly = make_poly(addr_prefix)
assert gf32_poly_eval(poly) == 0
@tevador

tevador commented Apr 13, 2026

Copy link
Copy Markdown
Author

Unresolved questions:

1. Is hiding amounts a sufficient level of privacy? RESOLVED

The current Jamtis proposal uses post-quantum encryption only for the amount and the key extensions. This stops a quantum attacker from learning transaction amounts and tracking the spends. However, they can still locate all enotes received to known addresses and recognize external enotes received to the wallet with a high probability (via full view tags).

Option 1A: Use CSIDH for amounts and key extensions

Keep the current proposal.

Option 1B: Use CSIDH for everything except primary view tags

Using CSIDH for primary view tags is not possible (explained in Appendix A). However, it would be possible to use it for secondary view tags and address tag encryption (and the rest). This would necessitate switching to CSIDH-512 instead of CSIDH-1024 to get acceptable scanning performance (amortized around 0.25 ms per enote with 8-bit primary view tags). The address size would stay the same.

Pros:

  • Quantum attackers cannot reliably recognize payments to known addresses. They only gain the equivalent of an external filter-assist tier via primary view tags, which gives a lot of false positives
  • Slightly smaller 2-output transactions (tx extra field is 68 bytes instead of 132 bytes)

Cons:

  • Lower post-quantum security (from 273 to about 261 T-gates)
  • Requires one Ze per output for transactions with >2 outputs. This would increase the new tx extra field size for 16-out transactions from 132 bytes to about 1030 bytes, which would bump into the current 1060-byte relay limit.

2. Should reduced keys be used for CSIDH-1024? [Applicable only to option 1A] RESOLVED

The current specs include reduced keys. This is a tradeoff, which reduces enote decryption time by about 30%, but requires keeping Zur secret. If Zur is ever leaked, address unlinkability security is reduced from 128 bits to 80-90 bits.

Option 2A: Use reduced keys for masking

Keep the current proposal.

Option 2B: Use full keys for masking

Pros:

  • Full address unlinkability even if the wallet public key Zur is leaked
  • Slightly simpler specification and implementation

Cons:

  • ~30% slower enote decryption (the number of field multiplications to calculate ssr4 would increase from 566 000 to 746 000)

3. How should the Jamtis upgrade be enforced?

Option 3A: Soft fork with key validation

As a soft fork, the upgrade would mandate the presence of the Jamtis extension field in all transactions as a consensus rule and the included CSIDH public key would need to pass validation. This achieves the maximum transaction uniformity, but has significant verification cost (about 10 ms per transaction with option 1A and about 1 ms per key with option 1B).

Option 3B: Soft fork without key validation

The soft fork would only mandate the presence of the Jamtis extension field in all transactions. The public key would not be validated, so in theory, invalid public keys would still leak some metadata.

Option 3C: Relay rule

The Jamtis extension field would be required via a relay rule, but this is a weaker form of enforcement.

Option 3D: No enforcement

Jamtis would simply be an optional wallet-side upgrade, but this risks transaction uniformity because Jamtis transactions would stand out in the blockchain due to the new tx extra field.

@tromp

tromp commented Apr 14, 2026

Copy link
Copy Markdown

Shouldn't the amount commitments be quantum proof as well?
Pedersen commitments can be opened arbitrarily once a quantum computer determines the discrete log of H.

@tevador

tevador commented Apr 14, 2026

Copy link
Copy Markdown
Author

Post-quantum soundness is out of scope of this proposal. I added section 1.3 to clarify this.

Both Carrot and Jamtis support a quantum-safe Pedersen commitment migration via a transparent turnstile because the blinding factor is constructed as:

ka = ScalarDerive("Jamtis commitment mask" || ssrctx || a || K1j || enote_type)

@tromp

tromp commented Apr 14, 2026

Copy link
Copy Markdown

Thanks for clarifying.

@kayabaNerve

Copy link
Copy Markdown

I'd argue the downsides to CSIDH-1024 are negligible, but I'd claim any forward-secrecy solution should be on the scale of 200+ years, e.g., that everyone who was a participant will be dead before it's revealed (if ever). To that end, I question if even CSIDH-1024 is acceptable. I'd also say hiding amounts alone is sufficient, yet your write-up notes hiding the address associated with outputs 'mandates' CSIDH-512 for performance reasons :/ I think we have to accept that any PQ solutions will have a performance hit and have to bite the bullet, even if seemingly infeasible.

As for enforcement, I'd say 3A or 3B.

@tevador

tevador commented Apr 19, 2026

Copy link
Copy Markdown
Author

@kayabaNerve

Appendix D lists the relevant data for option 1B, which includes post-quantum secondary view tags, which is a more private version than just encrypting amounts. Here I'm only considering CSIDH-512 or CSIDH-1024 for performance reasons. Note that the time scales for breaking CSIDH 512/1024 I'm listing are extremely conservative and might require implausibly fast scaling for quantum computers and an absurd amount of hardware even if we assume that 256-bit ECDLP is already broken by quantum computers.

If we think post-quantum encryption just for the amount is sufficient (option 1A), we could use CSIDH-2048. The impact of that choice in listed in Appendix B. Note that this option loses more privacy than 1B if ECDLP is broken.

Getting even more security than CSIDH-2048 would likely require switching to a lattice-based cryptosystem with considerable impact on address length and transaction sizes (see NTRU in Appendix A). Adding post-quantum view tags would additionally require accepting one of the following two sacrifices:

  1. Addresses that belong to the same wallet are easily linkable. -OR-
  2. Balance recovery for a wallet is O(N), where N is the number of addresses the wallet has generated.

AFAIK Zcash is going with the second option.

I'd claim any forward-secrecy solution should be on the scale of 200+ years

You have to consider the fact that Jamtis will be an optional upgrade. If we push for overkill security with very poor UX, users might just keep using legacy addresses and get no security at all.

@kayabaNerve

Copy link
Copy Markdown

I came back to try to leave a more helpful comment, and also saw your response which I didn't get a notification for (weird). I appreciate your commentary and am not here to reject what you're saying.

The more helpful comment I wanted to leave is:

  • We should have a comprehensive target for address unlinkability AND duration of secrecy, regardless of methodology.
  • Once we've agreed on the best option, our requirement is to implement it in a way acceptable.

I don't believe this is only true for addresses here and now. The exact same claim is true for when Monero adopts a fully PQ protocol. While with this half-step, we may have the option of only achieving part of our goals in exchange for better performance, we will not have that option latter.

Unfortunately, I cannot claim this decision to be easy or trivial. I understand how that's unhelpful of me and I can apologize there. Regarding implementation however, I have a much more hopeful opinion. Specifically, Monero's current processes are atrocious and deserve a complete rewrite almost regardless. If we are to consider the performance boons from GPUs, or not even GPUs yet even just SIMD, we can discuss ~doubling our throughput (assuming a 4-lane SIMD allows scanning 4 outputs in approximately just twice as long). That does imply a notable development effort to achieve such acceptable performance, but as I said, Monero will have to do such work regardless eventually. I'd rather hold ourselves to that standard now so we can accept it, and work past it, so when we will HAVE to do so, we have prior experience.

This likely makes me a 1B, 3B advocate, for at least CSIDH-1024. I understand if I'm being an extremist and this isn't realistic however, so I don't want to insist or get us sidetracked with something where while striving for perfection, we don't even get something good. I think with 3B (regardless of other options), the only downside is scanning speed, which has the benefit where we can improve that post-deployment, but yes, we'd still need users to opt-in. If we only predict this scheme to last for 3-5 years, then we can ask 'when would this post-deployment development actually occur', and accept something even as marginal as CSIDH-512, not because I personally could recommend it, but because I could recommend it over the addresses of today.

@tevador

tevador commented Apr 20, 2026

Copy link
Copy Markdown
Author

I've reopened this this MRL issue and posted more information there regarding the PQ method selection for Jamtis. It can be used for a more focused discussion.

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