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.
- 1. Introduction
- 2. Features
- 3. Notation
- 4. Wallets
- 5. Addresses
- 6. Addressing protocol
- 7. Security properties
- Credits
- References
- Appendix A: Selecting a post-quantum encryption scheme
- Appendix B: Interactive payment protocol
- Appendix C: Instant sync protocol
- Appendix D: Address prefix checksum
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:
- Wallets with publicly known addresses lose nearly all privacy against a quantum-enabled adversary.
- Wallets that use a third party service for scanning the blockchain lose nearly all privacy.
- Generating subaddresses requires keeping track of a global counter, which complicates implementations and may cause merchants to prefer legacy integrated addresses [4].
- The detection of outputs received to subaddresses is based on a lookup table, which can sometimes cause the wallet to miss outputs [5].
- Checking two addresses for equality is difficult for humans because CryptoNote addresses are long and case-sensitive.
The goal of Jamtis is to tackle the shortcomings of CryptoNote addresses that were mentioned above. Specifically:
- Jamtis wallets with publicly known addresses retain a certain level of privacy even against a quantum-enabled adversary.
- Jamtis wallets using a third-party scanning service retain a certain level of privacy.
- Jamtis addresses can be safely generated without keeping track of a global counter.
- Balance recovery for Jamtis wallets can be done reliably without the need to use a precomputed table of keys.
- 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:
- Backward compatiblity with Carrot without hard forking changes.
- Enotes sent to Jamtis addresses are indistinguishable from enotes sent to legacy addresses.
- 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.
An explicit non-goal of Jamtis is post-quantum soundness. This includes preventing a quantum-enabled adversary from:
- opening Pedersen commitments to arbitrary monetary values
- forging spend authorization proofs and linking tags
- 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.
Jamtis addresses start with the prefix xmr1 and consist of 400 alphanumeric characters. An example of an address: xmr1msfrcj9q4su57d8gujk1y0dhyiLwP8jCd6PC79rF1eg0NqdZ6pV0VyvbS3O9Lj2U3EcAUIkbTWQ2vjtTOslrvwWDpYKOmwo6N0IiPyO895hidBr9Q8CTrf8sFlkpIGkniA9jhh8KiDToxJhl07xPPsaegIhyyAfWstZXTLvmHjJ0EgJl1EUjDnCvNPziVOc3kSeW3fPBCxCAESvtRyunxjmID6BBzBWrG11qg0bRw9g3pNjQK4qy0wvyo3EJMJT01owyZeXAUcPHUCb0xTTY3OxMulHRK94RSJ8izidgneG9ibpqtxJiclsWoUA2F27V0WloLn7ToKd6mKPSM8J4Ld4z0RFXYc6Oyt91eEMxwbxzbIT6ddyNWNc7P3MtzHEBYDxL3w8KZubX
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.
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:
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).
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.
Jamtis introduces new wallet tiers that are useful for merchants.
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).
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.
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.
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.
- The function
BytesToInt(x)deserializes a little-endian integer from a byte stringx. - The function
IntToBytes(i)converts an integerito a little-endian byte string. - The function
RandBytes(b)generates a random b-byte string. - Concatenation is denoted by
||.
The function Hb(x) with parameters b, x, refers to the Blake2b hash function [8] initialized as follows:
- The output length is set to
bbytes. - Hashing is done in sequential mode.
- The Personalization string is set to the ASCII value "Monero", padded with zero bytes.
- The input
xis hashed.
The function SecretDerive is defined as:
SecretDerive(x) = H32(x)
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.
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:
i[31] &= 0x7f(clear the most significant bit)i[0] &= 0xf8(clear the least significant 3 bits)- 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:
i = BytesToInt(H32(x))- 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).
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 0xece55ed427012a9d89dec879007ebd7216c22bc86f21a080683cf25db31ad5bf06de2471cf9386e4d6c594a8ad82d2df811d9c419ec83297611ad4f90441c800978dbeed90a2b58b97c56d1de81ede56b317c5431541f40642aca4d5a313709c2cab6a0e287f1bd514ba72cb8d89fd3a1d81eebbc3d344ddbe34c5460e36453, a 1020-bit prime.
Valid CSIDH public keys are supersingular elliptic curves Z and are equal to the value of the curve coefficient. The starting curve is
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 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.
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.
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].
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.
Base32 in this specification uses the alphabet xmrbase32cdfghijknpqtuwy01456789. This alphabet was selected for the following reasons:
- The order of the characters has a unique prefix that distinguishes the encoding from other variants of "base32".
- The alphabet contains all digits
0-9, which allows numeric values to be encoded in a human-readable form. - Excludes the letters
o,l,vandzfor the same reasons as the z-base-32 encoding [16].
Base32 is used to encode the visual prefix.
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.
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.
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 |
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 |
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.
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)
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 |
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 |
This wallet tier can generate public addresses for the wallet. It doesn't provide any blockchain access.
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:
- 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.
- 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.
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.
This is a full view-only wallet that can see all incoming and outgoing payments (and thus can calculate the correct wallet balance).
This tier has full control of the wallet.
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.
The five public keys are constructed as:
K1j = kabj KsD2j = (1 / dabj) DurD3j = (1 / dabj) DfaD4j = (1 / dabj) DirZ5j = 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.
The 19-byte address tag tj = (j', vtaj) is constructed as follows:
j' = BlockEnc(sct, j)vtaj = H3("Jamtis address view tag" || sga || j')
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").
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.
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.
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.
The address tuple (tj, K1j, D2j, D3j, D4j, Z5j), a total of 275 bytes, is encoded in 370 base62 characters.
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 |
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.
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.
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.
The amount commitment is constructed as Ca = ka G + a H, where ka is the commitment mask and a is the amount.
The address tag j' is encrypted by exclusive or (XOR) with an encryption mask mj'.
The amount a is encrypted by exclusive or (XOR) with an encryption mask ma.
Transaction global fields are stored in "tx extra".
The classical ephemeral public keys De are stored the same way as in Carrot.
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.
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.
There are 2 enotes types: payment and change.
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).
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.
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 |
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)
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.
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:
- The FilterAssist wallet tier can still calculate the primary view tag with the knowledge of
sfa. - Change enote encryption/decryption can only be done by the ViewAll wallet tier.
- Internal payments can be encrypted/decrypted by the ViewReceived wallet tier, which is important for interactive payments.
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.
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.
| 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.
TODO
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.
- CryptoNote: https://github.com/monero-project/research-lab/blob/master/whitepaper/whitepaper.pdf
- Janus attack: https://www.getmonero.org/2019/10/18/subaddress-janus.html
- Carrot: https://github.com/jeffro256/carrot/blob/master/carrot.md
- Merchants and subaddresses: monero-project/meta#299 (comment)
- Subaddress lookahead issue: monero-project/monero#8138
- Polyseed: https://github.com/tevador/polyseed
- Carrot view tags: https://github.com/jeffro256/carrot/blob/master/carrot.md#734-view-tags
- Blake2: https://eprint.iacr.org/2013/322.pdf
- Ed25519: https://ed25519.cr.yp.to/ed25519-20110926.pdf
- Short Schnorr: https://eprint.iacr.org/2019/1105
- Curve25519: https://cr.yp.to/ecdh/curve25519-20060209.pdf
- CSIDH: https://eprint.iacr.org/2018/383
- CTIDH: https://eprint.iacr.org/2021/633
- Twofish: https://www.schneier.com/wp-content/uploads/2016/02/paper-twofish-paper.pdf
- ChaCha20-Poly1305: https://www.rfc-editor.org/info/rfc8439/
- Z-base32: http://philzimmermann.com/docs/human-oriented-base-32-encoding.txt
- UUID: https://en.wikipedia.org/wiki/Universally_unique_identifier
- Base58 overflow: https://github.com/monero-project/monero/blob/319b831e65437f1c8e5ff4b4cb9be03f091f6fc6/src/common/base58.cpp#L157
- McEliece: https://ipnpr.jpl.nasa.gov/progress_report2/42-44/44N.PDF
- Classic McEliece Round 4: https://classic.mceliece.org/nist.html
- HQC: https://pqc-hqc.org/doc/hqc_specifications_2025_08_22.pdf
- CRYSTALS-Kyber: https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf
- NTRU: https://csrc.nist.gov/CSRC/media/Projects/post-quantum-cryptography/documents/round-3/submissions/NTRU-Round3.zip
- NTRU Prime: https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf
- Swoosh: https://eprint.iacr.org/2023/271
- SIKE: https://csrc.nist.gov/csrc/media/Projects/post-quantum-cryptography/documents/round-4/submissions/SIKE-spec.pdf
- SIKE Attack: https://eprint.iacr.org/2022/975
- CSIDH quantum attack: https://eprint.iacr.org/2019/725
- ECDLP quantum attack: https://arxiv.org/abs/2306.08585
- CSIDH quantum circuit: https://eprint.iacr.org/2018/1059
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.
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:
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].
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].
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.
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:
- A CSIDH-1024 public key only increases the address length from 228 characters to 400 characters, which is acceptable.
- 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.
- Due to the ability to rerandomize keys, the encryption scheme can be secure even if the address generator wallet tier is compromised.
TODO
TODO
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.
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.
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.
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:
- Generate
seed = RandBytes(16) - Select the first
key_imagethat will be spent in the transaction - Let
ze = KeyDerive3("Jamtis IPP z_e" || svr || seed || "R" || key_image) - Let
Ze = ze * E - Let
doffer = KeyDerive2("Jamtis IPP d_offer" || svr || seed || t || D2) - Let
Doffer = doffer B - Let
macoffer = H16("Jamtis IPP mac_offer" || svr || seed || t || D2 || a || tag_size || "R" || key_image) - Let
soffer = SecretDerive("Jamtis IPP s_offer" || t || doffer D2) - 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.
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.
- Check that the address tag
tis correct. This is done by rederiving the address view tag as defined in § 5.1.2. - Decrypt
jfromj' - Let
daccept = (dur / dabj) - Let
soffer = SecretDerive("Jamtis IPP s_offer" || t || daccept Doffer) - Let
(seed, macoffer, a, key_image, tag_size, Ze) = AeadDec(soffer, ciphertextoffer, t || Doffer). Abort if the decryption fails. - Let
saccept = SecretDerive("Jamtis IPP s_accept" || t || daccept Doffer) - Let
de = KeyDerive2("Jamtis IPP d_e" || saccept || "R" || key_image || a) - Let
De = de B - Derive
ssrctxusing the rules from § 6.5 and § 6.7 for internal enotes. - Derive
Ko,Ca,aenc,vt1,vt2,j'encusing the rules from § 6.8. - Let
sigaccept = EdShortSign(PrivateToEdwards(daccept), a || t || D2 || "R" || key_image || Ko || vt || Ca || j'enc || aenc || De || tag_size || Ze) - 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.
In the SEND step, Alice decrypts Bob's ACCEPT message and submits the final Monero transaction.
- Let
doffer = KeyDerive2("Jamtis IPP d_offer" || svr || seed || t || D2) - Let
saccept = SecretDerive("Jamtis IPP s_accept" || t || doffer D2) - Let
(macoffer, a, key_image, tag_size, Ko, ka, aenc, vt, j'enc, sigaccept) = AeadDec(saccept, ciphertextaccept). Abort if the decryption fails. - Check
macoffer ?= H16("Jamtis IPP mac_offer" || svr || seed || t || D2 || a || tag_size || "R" || key_image) - Check that
key_imagecorresponds to an unspent enote in the wallet. - Let
Ca = ka G + a H - Let
de = KeyDerive2("Jamtis IPP d_e" || saccept || "R" || key_image || a) - Let
De = de B - Let
ze = KeyDerive3("Jamtis IPP z_e" || svr || seed || "R" || key_image) - Let
Ze = ze * E - Check
EdShortVerify(sigaccept, PublicToEdwards(D2), a || t || D2 || "R" || key_image || Ko || vt || Ca || j'enc || aenc || De || tag_size || Ze) - Submit the transaction to the Monero network.
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.
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
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.
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.
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.
# 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
Unresolved questions:
1.
Is hiding amounts a sufficient level of privacy?RESOLVEDThe 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:
Cons:
Zeper 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]RESOLVEDThe current specs include reduced keys. This is a tradeoff, which reduces enote decryption time by about 30%, but requires keeping
Zursecret. IfZuris 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:
Zuris leakedCons:
ssr4would 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.