Skip to content

Instantly share code, notes, and snippets.

@tevador
Last active May 22, 2024 05:53
Show Gist options
  • Save tevador/50160d160d24cfc6c52ae02eb3d17024 to your computer and use it in GitHub Desktop.
Save tevador/50160d160d24cfc6c52ae02eb3d17024 to your computer and use it in GitHub Desktop.

JAMTIS

This document describes a new addressing scheme for Monero.

Chapters 1-2 are intended for general audience.

Chapters 3-8 contain technical specifications.

Table of Contents

1. Introduction

1.1 Why a new address format?

Sometime in 2023, Monero plans to adopt a new transaction protocol called Seraphis [1], which enables much larger ring sizes than the current RingCT protocol. However, due to a different key image construction, Seraphis is not compatible with CryptoNote addresses. This means that each user will need to generate a new set of addresses from their existing private keys. This provides a unique opportunity to vastly improve the addressing scheme used by Monero.

1.2 Current Monero addresses

The CryptoNote-based addressing scheme [2] currently used by Monero has several issues:

  1. Addresses are not suitable as human-readable identifiers because they are long and case-sensitive.
  2. View-only wallets need key images to be imported to detect spent outputs [3].
  3. Too much information about the wallet is leaked when scanning is delegated to a third party.
  4. Generating subaddresses requires view access to the wallet. This is why many merchants prefer integrated addresses [4].
  5. Addresses are susceptible to man-in-the-middle (MITM) attacks [5].
  6. Subaddresses that belong to the same wallet can be linked via the Janus attack [6].

1.3 Jamtis

Jamtis is a new addressing scheme that was developed specifically for Seraphis and tackles all of the shortcomings of CryptoNote addresses that were mentioned above. Additionally, Jamtis incorporates two other changes related to addresses to take advantage of this large upgrade opportunity:

  • A new 16-word mnemonic scheme called Polyseed [7] that will replace the legacy 25-word seed for new wallets.
  • The removal of integrated addresses and payment IDs [8].

2. Features

2.1 Address format

Jamtis addresses, when encoded as a string, start with the prefix xmr and consist of 142 or more base58 characters. Example of an address: xmred2JmtmdKYheeD3fTsYGphSEcMM8z95mNA52W8anhmByRenUpEzNEoHdvqGTWR6L2jWvXmadABC1b3KyMfznQ2XJgwZSwCFiELJMwG4mc2xjGn5N8keKEMgS15DsGdG7MGYEDxBrhjs

There is no longer any "main address" - all Jamtis addresses are equivalent to a subaddress.

2.2 Recipient IDs

Jamtis introduces a short recipient identifier (RID) that can be calculated for every wallet and every address. RID consists of 25 case-insensitive alphanumeric characters that are separated by hyphens for better readability. The RID for the above address is h8eug-w77qs-aaf7m-ww63i-hn33c. Instead of comparing long addresses, users can compare the much shorter RID. RIDs are also suitable to be communicated via phone calls, text messages or handwriting to confirm a recipient's address. This allows the address itself to be transferred via an insecure channel.

2.3 Invoices

Addresses can encode metadata such as an amount, timestamp or a short description. Such addresses can be referred to as Monero invoices. For example, the following address encodes an amount of 0.001 XMR and the message "THIS IS A TEST PAYMENT": xmrhtx7H1uwbArRUAVNjfwTYAj5kmXKCgZcR6jtGnjB8eMH3RihMnhos4f6Gz7gbj5pgNdbxr5xuPAjRT9xcRL2X9ta5ShB2sQbiMDVvECXbUXLp4cLYQTJAJ36gRKXZaTfEtfJd1oQFJSpZ2fSp1YgZHWzGh4za35MQYJQ.

2.4 Payment URIs

A Monero invoice can be transformed into a payment URI by replacing the xmr prefix with the monero: URI scheme: monero:htx7H1uwbArRUAVNjfwTYAj5kmXKCgZcR6jtGnjB8eMH3RihMnhos4f6Gz7gbj5pgNdbxr5xuPAjRT9xcRL2X9ta5ShB2sQbiMDVvECXbUXLp4cLYQTJAJ36gRKXZaTfEtfJd1oQFJSpZ2fSp1YgZHWzGh4za35MQYJQ.

Payment URIs can be used online as clickable links in the web browser and if a protocol handler has been installed, the address wlll be sent directly to the wallet software.

2.5 QR Codes

Thanks to a more efficient metadata encoding, Jamtis payment URI QR codes have similar sizes to legacy payment URI QR codes. Additionally, Jamtis supports a binary format for Monero invoices, which can be used by specialized applications and results in smaller QR codes.

QR Code

2.6 Signed addresses

To protect from MITM attacks, Jamtis addresses can be optionally signed by the owner of the address. The main use cases are:

2.6.1 Identity verification

When Alice and Bob meet, Bob can write his RID on a piece of paper and give it to Alice. When Bob sends Alice a Monero invoice in the future, Alice will know the address belongs to Bob because it is signed with a key that matches the RID Bob gave her.

2.6.2 Undeniable payments

When proving a payment [9], Alice can convince Charlie that the address she made a payment to belongs to Bob, because the address was signed by Bob's key. If the address were not signed, Bob could claim that the address is not his, but Alice's own address.

2.6.3 Online commerce

Dave runs an online shop at https://eshop.example.com. To assure his customers, he can create a special DNS record on the domain name eshop.example.com that contains his Monero public key and he can provide signed Monero invoices to all his customers. When shopping at Dave's website, Alice can feel safe to send her Monero to the provided address, because her wallet software will confirm that the address is owned by the domain eshop.example.com.

2.7 Light wallet scanning

Jamtis introduces a new wallet tier below "view-only wallet". This access tier (also called "Tier 1") is intended for wallet-scanning and only has the ability to calculate view tags [10]. It cannot generate wallet addresses or decode output amounts.

View tags can be used to eliminate 99.6% of outputs that don't belong to the wallet. If provided with a list of wallet addresses, Tier 1 can also link outputs to those addresses. Possible use cases are:

2.7.1 Wallet component

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

2.7.2 Third party services

If the Tier 1 private key is provided to a 3rd party, it can preprocess the blockchain and provide a list of potential outputs. This reduces the amount of data that a light wallet has to download by a factor of at least 256. The third party will not learn which outputs actually belong to the wallet and will not see output amounts.

2.8 Full view-only wallets

Jamtis view-only wallets (also called "Tier 2") provide access to all wallet features except of the ability to spend. Notably, they 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.9 Merchant wallets

Jamtis introduces a new wallet type for merchants. Merchant wallets use a slightly different key derivation that allows for two additional wallet access tiers:

2.9.1 Address generator

This tier (also called "Tier 0") is intended for merchant point-of-sale terminals. It can generate addresses for a specific account on demand, but otherwise has no access to the wallet (i.e. it cannot recognize any payments in the blockchain or generate addresses for other wallet accounts).

2.9.2 Payment validator

This wallet tier (also called "Tier 1.5") combines Tier 0 (generating addresses) with the ability to also view payments received to the generated addresses. It is intended for extended point of sale use (for example, it can detect that a specific order has been paid). It cannot see outgoing payments, received change outputs or payments received to other wallet accounts.

2.10 Janus attack mitigation

Janus attack is a targeted attack that aims to determine if two addresses A, B belong to the same wallet. Janus outputs are crafted in such a way that they appear to the recipient as being received to the wallet address B, while secretly using a key from address A. If the recipient confirms the receipt of the payment, the sender learns that they own both addresses A and B.

Jamtis prevents this attack by allowing the recipient to recognize a Janus output.

Merchant wallets can only detect a Janus attack that aims to link two addresses from different accounts. Addresses of merchant wallets that belong to the same account are already linkable off-chain.

3. Notation

3.1 Serialization

  1. Fixed-size integers are serialized in little endian byte order.
  2. Private keys are serialized as 256-bit integers.
  3. String constants are serialized in ASCII encoding and always include an implicit null byte at the end.
  4. Elliptic curve points are serialized as 256-bit integers, with the lower 255 bits being the y-coordinate of the point and the most significant bit being the parity of the x-coordinate.

3.2 Hash functions

The function H(...) refers to the keccak-256 hash function. It accepts an arbitrary number of arguments, which are serialized and concatenated before hashing. The following 3 other hash functions based on H are used:

  1. Hb(...) where b is an integer divisible by 8, refers to the hash function H with the output truncated to b bits (e.g. H32(...))
  2. Hs(...) refers to a "hash to scalar" function, which interprets the output of H as a serialized 256-bit integer and then reduces it modulo a prime number , which must be relatively close to a power of 2.
  3. Hp(...) refers to an unspecified "hash to point" function, which outputs elliptic curve points.

3.3 Elliptic curve

This specification assumes the use of the ed25519 elliptic curve, which includes a cyclic subgroup 𝔾 of order ℓ = 2252 + 27742317777372353535851937790883648493.

  1. Uppercase letters usually refer to elements of 𝔾.
  2. Lowercase letters usually refer to elements of Z (scalars).
  3. Scalar multiplication is denoted by a space between the scalar and the group element, e.g. K = k G.
  4. Scalar multiplication may be prepended with the number 8, which means that the point is also multiplied by the ed25519 cofactor to ensure the result belongs to the group 𝔾.

3.3.1 Base points

The following three base points are used:

Point Derivation Serialized (hex)
G curve generator 5866666666666666666666666666666666666666666666666666666666666666
U Hp("seraphis U") 126582dfc357b10ecb0ce0f12c26359f53c64d4900b7696c2c4b3f7dcab7f730
X Hp("seraphis X") 4017a126181c34b0774d590523a08346be4f42348eddd50eb7a441b571b2b613

4. Wallets

4.1 Wallet parameters

Each wallet consists of three private keys, a timestamp and a boolean flag:

Field Type Description
km private key wallet master key
kvb private key view-balance key
kfr private key find-received key
created_on timestamp date when the wallet was created
merchant boolean whether this is a merchant-type wallet

The master key km is required to spend money in the wallet ("Tier 3" access). The view-balance key kvb provides full view-only access ("Tier 2"). Finally, the find-received key kfr provides the ability to calculate the sender-receiver shared secret ("Tier 1" access).

The created_on timestamp is important when restoring a wallet and determines the blockchain height where scanning for owned outputs should begin. The merchant flag determines if this is a merchant wallet (affects the way addresses are derived).

4.2 New wallets

4.2.1 Standard wallets

Standard Jamtis wallets are generated as a 16-word Polyseed mnemonic [7], which contains a secret seed value used to derive the wallet master key and also encodes the date when the wallet was created and the merchant flag. The keys kvb and kfr are derived from the master key.

Field Derivation
km polyseed_key mod ℓ
kvb kvb = Hs("view-balance key", km)
kfr kfr = Hs("find-received key", kvb)
created_on from Polyseed
merchant from Polyseed

4.2.2 Multisignature wallets

Multisignature wallets are generated in a setup ceremony, where all the signers collectively generate the wallet master key km and the view-balance key kvb. The find-received key is derived from the view-balance key.

Field Derivation
km setup ceremony
kvb setup ceremony
kfr kfr = Hs("find-received key", kvb)
created_on setup ceremony
merchant setup ceremony

4.3 Migration of legacy wallets

Legacy pre-Seraphis wallets define two private keys:

  • private spend key ks
  • private view-key kv

4.3.1 Standard wallets

Legacy standard wallets can be migrated to the new scheme based on the following table:

Field Derivation
km km = ks
kvb kvb = Hs("view-balance key", km)
kfr kfr = Hs("find-received key", kvb)
created_on entered manually
merchant entered manually

Legacy wallets cannot be migrated to Polyseed and will keep using the legacy 25-word seed.

4.3.2 Multisignature wallets

Legacy multisignature wallets can be migrated to the new scheme based on the following table:

Field Derivation
km km = ks
kvb kvb = kv
kfr kfr = Hs("find-received key", kvb)
created_on entered manually
merchant entered manually

4.4 Wallet public keys

There are 3 global public keys. These keys are not usually published.

Key Name Value
Ks wallet spend key Ks = kvb X + km U
KID wallet identity key KID = kvb G
Kfr find-received key Kfr = kfr G

The keys Ks and Kfr are required by lower wallet tiers. The wallet identity key KID can be optionally published and used to prove that addresses belong to a specific wallet (§ 7.4).

For better UX when opening or restoring a wallet, the wallet is identified by the hash value H128("Monero RID", KID) encoded in base-32 using the ID32 scheme [11].

5. Addresses

5.1 Address generation

Each address is generated from two 32-bit indices i,j, where i is the index of the account and j is the index of the address within the account.

Each Jamtis address encodes three public keys:

  • K1i,j = Ks + kxi,j X
  • K2i,j = kai,j Kfr
  • K3i,j = kai,j G

The private keys kai,j and kxi,j are derived as follows:

Keys Name Derivation if(merchant) Derivation if(!merchant)
kai,j authentication keys kai = Hs("auth key", kvb, i) kai,j = Hs("auth key", kvb, i, j)
kxi,j spend key extensions kxi,j = Hs("key extension", kai, j) kxi,j = Hs("key extension", kvb, i, j)

Note that for merchant wallets, the authentication keys kai don't depend on the index j, so they are the same for all addresses of a given account. This also means that merchant addresses with the same index i are easily linkable off-chain (they share the same public keys K2 and K3).

5.2 Sending to an address

When sending amount a to an address (K1, K2, K3), the sender does the following:

  1. Generate a random nonzero scalar r from Z.
  2. Calculate the ephemeral public key Ke = r K3
  3. Calculate the derived key Kd = 8*r K2
  4. Calculate the view tag v = H8("view tag", Kd)
  5. Calculate the shared secret q = Hs("sender-receiver secret", Kd)
  6. Derive a one-time output public key Ko = K1 + q X
  7. Calculate the blinding factor b = Hs("blind", q, r G)
  8. Encrypt the amount a~ = a XOR H64("amount", q, r G)
  9. Calculate the amount commitment C = b G + a H
  10. Output (Ke, v, Ko, a~, C)

5.3 Receiving an output

The receiver does the following to examine a potential output (Ke, v, Ko, a~, C):

  1. Calculate the nominal derived key Kd' = 8*kfr Ke
  2. Calculate the nominal view tag v' = H8("view tag", Kd')
  3. If v' != v, abort.
  4. Calculate the nominal shared secret q' = Hs("sender-receiver secret", Kd')
  5. Calculate the nominal spend key Ks' = Ko - q' X
  6. Try to find indices i,j such that K1i,j = Ks' (this is usually done by a hashtable lookup).
  7. If no spend key is found, abort.
  8. Derive r' G = Ke / kai(,j)
  9. Decrypt the nominal amount a'= a~ XOR H64("amount", q, r' G)
  10. Calculate the nominal blinding factor b' = Hs("blind", q, r' G)
  11. Calculate the nominal amount commitment C' = b' G + a' H
  12. If C' != C, abort (possible Janus attack).
  13. Calculate the partial private spend key ksp = kvb + kxi,j + q
  14. Derive the linking tag Kt = (Ks - kvb X) / ksp
  15. Set the boolean s to the spend status of the linking tag Kt
  16. Output the private values (a, b, i, j, ksp, s)

5.4 Change and self-spends

Change outputs and self-spends are special because the sender is the same as the receiver. There are heuristics that can be applied by Tier 1 wallets to recognize when outputs are being spent based on the presence of outputs that send funds back to the wallet [12].

To protect from such attacks, the output construction and recognition is modified for change and self-spends as follows:

5.4.1 Output construction

  1. For change outputs, the shared secret from § 5.2.5 is calculated as q = Hs("change secret", kvb, Ke)
  2. For self-spends, the shared secret from § 5.2.5 is calculated as q = Hs("self-spend secret", kvb, Ke)
  3. The blinding factor from § 5.2.7 is calculated as b = Hs("blind", q)
  4. The encrypted amount from § 5.2.8 is calculated as a~ = a XOR H64("amount", q)

The distinction between change outputs and self-spends is done to preserve the transaction history (self-spends should be visible in the history, change outputs not).

5.4.2 Output recognition

Change outputs and self-spends can only be detected by Tier 2+ wallets because it requires the private key kvb. Whenever an output with a matching view tag is discovered in a transaction that spends a previous wallet output, the shared secret calculation from § 5.3.4 is replaced with the modified calculation from § 5.4.1. The wallet will first try to find the key corresponding to a change output and if that fails, it will try the key corresponding to a self-spend.

5.5 Transaction size

Jamtis enables a small reduction in average transaction size (excluding any other Seraphis-related changes).

5.5.1 Transactions with 2 outputs

The encrypted payment ID can be removed from all 2-output transactions, saving 9 bytes (this includes the TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID tag and the actual 64-bit payment ID).

Transactions with 2 outputs only need one value of Ke (§ 5.2.2) as the change output can use the same value. This public key can use the existing TX_EXTRA_TAG_PUBKEY field.

5.5.2 Transactions with 3 or more outputs

Since there are no "main" addresses anymore, the TX_EXTRA_TAG_PUBKEY field can be removed from transactions with 3 or more outputs, saving 33 bytes.

Transactions with 3 or more outputs will need one value of Ke (§ 5.2.2) per output. These public keys can use the existing TX_EXTRA_TAG_ADDITIONAL_PUBKEYS field.

6. Wallet tiers

Tier Knowledge Off-chain capabilities On-chain capabilities
0* kai, Ks, Kfr generate and sign addresses for account i none
1 kfr recognize all wallet addresses (but cannot generate them) eliminate 99.6% of non-owned outputs (up to § 5.3.5), link output to an address (except of change and self-spends)
1.5* kfr, kai, Ks Tier 0 + Tier 1 Tier 1 + view received for account i (up to § 5.3.12)
2 kvb, Ks recognize and generate all addresses, certify Tier 0* view wallet balance (up to § 5.3.16), including change and self-spends
3 km Tier 2 Tier 2 + spend

* Merchant wallets only

7. Address encoding

7.1 Address structure

An address has the following overall structure:

Field Size (bytes) Description
Header 1 network type, metadata content, signature type (§ 7.2)
K1 32 address spend key
K2 32 address exchange key
Metadata 0-190 optional amount, timestamp and description (§ 7.3)
Signature 32-132 (§ 7.4)
Checksum 4 (§ 7.5)

7.2 Address header

The 1-byte header has the format of 1NNMMMSS, where NN is the 2-bit network type identifier, MMM are the metadata flags and SS is the 2-bit signature type identifier. The most significant bit of the header byte is always 1, which allows for easy disambiguation from the legacy Monero address, which always starts with a 0 bit.

7.2.1 Network type

NN network type
00 invalid
01 testnet
10 stagenet
11 mainnet

The invalid network type 00 can be used in the future to extend the header to more than 1 byte, if needed.

7.2.2 Metadata

The metadata flags MMM simply indicate if the field is present in the address (1 for present, 0 for omitted).

bit position field
0 amount
1 timestamp
2 description

7.2.3 Signature type

There are 4 address signature types:

SS signature type description
00 none the address is not signed
01 local signature the address is signed with kai(,j)
10 global signature the address is signed with kvb
11 local signature with a certificate the address is signed with kai(,j), K3 is signed with kvb

7.3 Metadata

7.3.1 Amount to pay

If this field is not present, the wallet software should prompt the user to enter an amount. If present, the amount is displayed and confirmed by the user.

Because typical payment amounts tend to have a low number of significant digits, the amount in atomic units is encoded as a = (v + 1) * 10e, where e is a 4-bit exponent value (0-15) and v is encoded as a varint in little endian format, with the first byte encoding 3 bits and each additional byte encoding 7 bits (the 9th byte encodes full 8 bits). Values with 3 significant digits, such as 1.23 XMR, can be encoded in just 2 bytes. Zero amount cannot be encoded (the amount field should be omitted instead).

7.3.2 Timestamp

The timestamp generally means the date until when the address is valid. If the date is present and is in the past, the user should be notified that the address has expired. This field can be used as a 'due date' for invoices.

The timestamp is encoded in Unix format as a 32-bit unsigned integer.

7.3.3 Description

The description is an arbitrary text describing the purpose of the payment. The permitted set of characters is: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ @%,+-./: and the maximum supported length is 256 characters.

The description is encoded with length - 1 in a single byte and then encoding pairs of characters with 11-bit symbols (5.5 bits per character). This works because 442 < 211. To encode a pair of characters c1, c2, they are first converted to their corresponding base-44 values v1, v2 and then encoded as the 11-bit number 44 * v2 + v1. If the description contains an odd number of characters, the string is padded with a space character before encoding (this space can be removed when decoding based on the length prefix). An empty string cannot be encoded (the description field should be omitted instead).

7.4 Signature

The interpretation of the signature field depends on the signature type specified in the address header.

7.4.1 No signature

If no signature was specified, then this field is simply the encoding of the public key K3.

7.4.2 Local signature

Local signature means that all the data preceding the signature field are signed with the private key kai(,j). The signature is encoded as (R, s), where:

  • r = Hs("signature nonce", kai(,j), data) is a pseudorandom nonce
  • R = r G
  • s = r + Hs("Monero address signature", data) * kai(,j)

The sender can recover the required public key as K3 = (s G - R) / H("Monero address signature", data).

7.4.3 Global signature

Global signature means that all the data preceding the signature field and the key K3 are signed with the private key kvb. The signature is encoded as (K3, R, s), where:

  • r = Hs("signature nonce", kvb, data) is a pseudorandom nonce
  • R = r G
  • s = r + Hs("Monero address signature", data, K3) * kvb

The sender can recover the wallet identity key as KID = (s G - R) / Hs("Monero address signature", data, K3) and use it to authenticate the recipient.

7.4.4 Local signature with a certificate

This variant is like the local signature, except the local signing key K3 is certified by the wallet-global key kvb. The signature field is the following tuple: (R1, s1, t, R2, s2), where:

  • r1 = Hs("signature nonce", kai(,j), data) is a pseudorandom nonce generated by the local signer
  • R1 = r1 G
  • s1 = r1 + Hs("Monero address signature", data) * kai(,j)
  • t is a timestamp limiting the validity of the certificate (32-bit unsigned integer)
  • r2 = Hs("signature nonce", kvb, K3, t) is a pseudorandom nonce generated by the global signer
  • R2 = r2 G
  • s2 = r2 + Hs("Monero address certificate", K3, t) * kvb

The public keys can be recovered from the signatures in similar ways as above. If the timestamp t is in the past, the signature should be treated as a local signature instead.

7.5 Checksum

The purpose of the checksum is to prevent accidental corruption of the address. The checksum is calculated as a hash H32("Monero address checksum", data), where data refers to all the preceding parts of the address.

7.6 Binary-to-text encoding

An address can be encoded into a string as follows:

address_string = "xmr" + base58(address_binary)

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 [13], so legacy Monero software can never accidentally decode a Jamtis address.

7.7 Recipient authentication

7.7.1 Recipient identifiers

Because addresses are bulky and opaque, Jamtis defines a concise, more human-friendly identifier for each address, called the Recipient identifier (RID). RIDs are calculated as H128("Monero RID", input) and encoded using the ID32 scheme [11]. The input depends on the address signature type:

signature type input
none whole address except of the checksum
local K3
global KID
local with certificate KID

A local signature with a certificate that has expired is considered to be just a local signature (RID is calculated from K3).

7.7.2 Validating an RID

There are 4 ways how an RID may be validated when sending to an address:

  1. It may be already present in the sender's address book.
  2. It may be entered manually by the user. In this case, it's best to obtain the RID from the receiver using a different communication channel than the one used to transfer the address.
  3. The user may enter a domain name and the corresponding public key is extracted from a DNS TXT record.
  4. The user may enter an onion address and the corresponding public key is decoded from the onion address (v3 onion addresses encode an ed25519 public key that's also usable in Monero).

7.7.3 Authentication workflow

When sending to an address, wallet software should follow this authentication workflow:

  1. The address is parsed and verified that it's well-formed.
  2. If the address timestamp is present and the address is expired, the user is notified and given the option to continue or abort. (Note: this validation applies to the timestamp from the Metadata section, not the certificate timestamp).
  3. The RID is calculated.
  4. If the RID is present in the local address book, the recipient's name is loaded and displayed with a green check mark. Skip to step 8.
  5. The user is presented with a "recipient validation dialog", where they are asked to enter an RID or a domain name.
  6. If the address RID matches the one that was entered or obtained via DNS or the onion domain, the RID is displayed as validated with a yellow check mark. Skip to step 8.
  7. If the RID doesn't match the address, no DNS record was found or the user dismisses the dialog, the recipients RID is displayed with a red cross mark as "unverified".
  8. The address amount and description are displayed, if present. If the amount is missing, the user is asked to enter it.

8. Test vectors

8.1 New wallet

The test vectors in this section are for a new wallet restored from the Polyseed phrase: mask solution glory route degree near mirror vessel coast rain fee basic detail asthma course idle.

8.1.1 Wallet1 RID

a86pw-bdm77-jejzs-sd4ky-qr6g5

8.1.2 Address 1

Address (0,0), no metadata, no signature

e0 eb f6 77 a3 05 f1 76 e1 0b 3c bd 06 fd 3e a2
96 de 3b 68 6f e6 97 cd 36 38 f3 ee 03 b1 59 9c
93 61 a2 fa 27 fd cc 88 dc c7 67 f6 c9 f9 8b 78
b2 e5 c6 ee b9 b9 e6 8e 0d e9 ad c3 a7 93 fd 89
ee ca 2b 49 cf 48 14 5b 7d 2a 4e 02 e6 59 79 83
1a 17 c7 44 83 83 44 16 19 3d a0 95 16 47 a2 8f
72 cb e1 0b fe

(101 bytes)

xmred2JmtmdKYheeD3fTsYGphSEcMM8z95mNA52W8anhmByRenUpEzNEoHdvqGTWR6L2jWvXmadABC1b3KyMfznQ2XJgwZSwCFiELJMwG4mc2xjGn5N8keKEMgS15DsGdG7MGYEDxBrhjs (142 characters)

RID: h8eug-w77qs-aaf7m-ww63i-hn33c

8.1.3 Address 2

Address (0,1), amount 0.001, locally signed

f1 7f d6 94 15 32 94 b3 92 49 60 ec 2b aa 4e 07
fb 97 79 6e fa 70 a2 0e 22 50 42 03 bd eb 52 84
0e 81 56 b4 3c b3 1e ac 1f 8a 6e 53 71 6a e4 c7
da d5 79 e5 1a d6 c2 70 9c 5e 08 31 06 3d 61 ef
1a 90 ee 8d 2c 45 d8 d6 69 19 85 51 47 80 8a 3d
4b f3 64 eb 65 2d 57 3c 90 a9 68 c5 77 f4 fd 7b
d6 1c b8 8a b6 94 a4 ca 66 95 56 db bc de 66 9e
5b 61 b7 2f 1a 7b 59 53 36 ba 56 9a 29 30 23 60
1b 0b 97 6e ee 5d

(134 bytes)

xmrhPr6EbTTmNJRUAVNjfwTYAj5kmXKCgZcR6jtGnjB8eMH3RihMnhos4f6Gz7gbj5pgNdbxr5xuPAjRT9xcRL2X9ta5Sj3MEWonwfJabis2wohsSDhpR4piQXSKRCQDPNEFvnvcpA5L7KyWUyJABrGgqV2VbGHWz6f5SMuLA9vtFkAt5iX1EU8YSoVN (188 characters)

RID: htgin-5gs6p-u7kwx-t9dii-cakhz

8.1.4 Address 3

Address (1,1), amount 0.09, description TEST ADDRESS3, locally signed

f5 1b cf 35 a1 55 d4 3d b5 36 44 9b 4e b6 3d 4b
24 f4 93 7e 48 e7 9c 64 02 26 11 94 85 b1 38 51
09 66 ee 32 64 dd 00 1a 53 54 83 d0 c7 c3 57 c3
3d a1 9d 74 e1 30 ff 58 74 ce e8 45 fe 00 36 92
33 a8 01 0c 85 c2 28 77 92 34 28 76 ce 18 4a 5c
e4 54 7b 42 4e d4 1d 2f 62 62 57 36 5f cd fe 47
5c 50 94 06 fd 29 7c 3f 76 50 0e 73 be f8 b9 e5
69 12 65 35 e3 32 c9 d5 5e 5a 69 71 8c 52 4a 7a
86 51 9c 12 b0 79 7c 77 12 82 2c ca b8 0e 2e 23
28 f8

(146 bytes)

xmrhzrvZxUYMzUXJz2ddDEu1t7BWnCNSnJ5Z1Mr23Sha8Ti2aDHkiRFWTKEwQbAFDCgbUBJuFd5HmpQfLYBoGX8cdhB9e8ZxgUhze2RTMrAosLfG7fC63vAGLBXtHTT4tJBn2jQGSZzSGASqNeLnnLHHqdbm6JaL4WcnVG9SGnLx4fQwz81PU4WWQzJ1YS46ZE2NGvsv247q (204 characters)

RID: ax7sb-1kd5a-fchxd-qz7bz-fkuue

8.1.5 Address 4

Address (1,2), no metadata, globally signed

e2 bd 23 81 13 c1 e9 7f 68 ad 88 1e eb d9 3d b4
89 f9 0f a2 1b 3e 00 36 75 08 5c f3 26 76 ac af
28 3f b7 01 41 4e 07 d3 4f 77 93 77 b8 ec 5f 7c
c1 b2 10 fb 17 5f 86 91 34 84 c2 78 c6 d9 5e a1
63 59 f4 4f c3 f1 81 70 99 ba 9e d5 14 be a5 14
c5 3a d0 3a 24 69 d8 ba 5f 98 61 4b 89 72 2f f5
e4 cd a5 f8 9c 76 d5 0d 98 78 9b 5a ca c0 7d c8
45 b1 89 58 36 10 f4 d3 6f ff 0b 6d 86 84 d7 23
5e 9d c7 a1 16 5f 3c 81 0c 95 ce 6f 75 6e aa c4
d0 f6 ff 35 ba 82 43 2d b6 44 be 6f 92 45 03 a8
0c ee c1 ac d5

(165 bytes)

xmrevejeNTBpQWJWWNN18Mds1Q5WaLq1wqN5LaN5m1idpSz7jTwxizJJbcEHvxiYk5HkFZQ6CgAGZVfv9nVmzewKBCkHcqJof7k46BSiN6bqJLny9ZzNnJak1VtmGzPpp5ragoNfGgNL6YPVwaSWAL995YVhqCf7dQaSod3pKjWEmbqe2scGpu2AGyEGpQ376LZhT52bmbxDoC4EFnhiXVEX3KkWwvP2TdM4sv (230 characters)

RID: a86pw-bdm77-jejzs-sd4ky-qr6g5

8.2 Legacy wallet

The test vectors in this section are for a non-merchant legacy wallet restored from the seed whipped patio problems aztec jaws joyous daytime pitched psychic sawmill gumball factual abbey unwind chlorine exotic number ditch fountain farming timber haggled anecdote assorted haggled.

8.2.1 Wallet2 RID

7ucci-9q61x-cz6ir-848di-sype5

8.2.2 Address 5

Address (0,0), no metadata, no signature

e0 92 a2 e7 16 85 21 0c f2 d8 68 34 a1 29 e4 50
3e 93 60 5b 0f 7e b1 9d 80 02 ab 84 3e fe 11 fe
6f bb 2f 37 13 c7 79 6e 5d e8 e7 34 8d 20 75 77
8f f1 88 8a 40 49 37 20 9f 88 7c 3a 83 15 0c 90
78 29 5a 19 de ed 8a a0 19 14 17 ee e0 97 c7 24
15 1c 07 b9 d7 32 d0 b2 53 88 43 0f 5a 17 7b ed
81 9a 61 b4 cc

(101 bytes)

xmreZdyPtmpsaKhcuS43FSVh9BU4dUxqoYDzNQry9rraupqKgw5v3BSQUDGi3TujUJ6viR5S4nn1q46sTgfwRisYu8KM6icEsrpbif5CHysqC7brb4XnjbTvQqZBEyNKuDK2HT2Fd5SzH1 (142 characters)

RID: bnu8d-s8d1e-hda8s-3s97m-63tor

8.2.3 Address 6

Address (0,1), no metadata, globally signed

e2 6a 64 4e 52 4d 25 63 23 7c 8b 36 0c 49 ca ae
8a 7c fd 59 0e c3 7a de 38 d7 03 fb 45 85 b9 a0
8e ae 1f d9 bf 62 bd 7b 6e 25 f9 51 a1 64 02 d9
3d d6 28 85 16 de 57 9d c3 bc 05 bb 5b 8f 34 ab
b7 91 67 00 d6 62 fc d5 32 65 88 b1 32 c2 8b 3a
b3 df 46 8e c9 2a df 02 2f e2 80 9e fd a1 c5 3c
79 4d 04 01 86 49 ea e1 f2 a5 e9 ee 02 db d2 da
ee ce 6c 8d 06 bf 47 50 6e 36 cb 32 21 2f a0 5c
21 b9 98 d8 f2 89 2d f1 fa 72 bc 71 49 85 0e 9e
77 6a bf 2d 24 25 0f a4 1c 4c a6 61 8d 60 ae f6
05 83 24 00 cb

(165 bytes)

xmresWs2koGPgW6wGHPuyPkS9QAWYfbDJMQqAWRG5KkxxymQsBESz1GySzKRaTTuFS6sABLtjroTah1vZjsRuak2Wr6XhqgWtnsRKa9RuoJFYyW33X5z9nT4fR9s91YTJWXp1CfMHmfgPhE7EGhazT9PGUL6dgwioMcJtqWPKSDRfZHNKA36eB8UEpGjweitfM6gP7YUuLyVfnvsZoWT5jYJmDacZuf1d55d2a (230 characters)

RID: 7ucci-9q61x-cz6ir-848di-sype5

References

  1. https://github.com/UkoeHB/Seraphis
  2. https://bytecoin.org/old/whitepaper.pdf
  3. https://www.getmonero.org/resources/user-guides/view_only.html
  4. monero-project/meta#299 (comment)
  5. https://www.reddit.com/r/Monero/comments/mcvuxc/beware_crypto_stealing_malware/
  6. https://web.getmonero.org/2019/10/18/subaddress-janus.html
  7. https://github.com/tevador/polyseed
  8. monero-project/monero#7889
  9. https://www.getmonero.org/resources/user-guides/prove-payment.html
  10. monero-project/research-lab#73
  11. https://github.com/tevador/id32
  12. https://gist.github.com/tevador/50160d160d24cfc6c52ae02eb3d17024#gistcomment-4006358
  13. https://github.com/monero-project/monero/blob/319b831e65437f1c8e5ff4b4cb9be03f091f6fc6/src/common/base58.cpp#L157
@tevador
Copy link
Author

tevador commented Sep 27, 2023

We can't do this because if you have a 2-output tx (using a shared xK_e), and one of your outputs is a self-spend and the other is a change output, Ko will be shared between the enotes

This can be fixed by including the output index in the shared secret calculation. That would make both outputs have unique K_o.

In fact, the same problem applies to normal enotes. If someone sends a 2-out tx where both outputs go to the same address, both outputs will have the same K_o. This is non-standard, but AFAIK the protocol allows it. This shows that the input_context that only consists of key images is insufficient to ensure the uniqueness of K_o and fails to prevent "the burning bug".

@jeffro256
Copy link

In fact, the same problem applies to normal enotes. If someone sends a 2-out tx where both outputs go to the same address, both outputs will have the same K_o. This is non-standard, but AFAIK the protocol allows it.

The protocol doesn't allow it though. One of the rules of Jamtis is that every transaction contains at least one self-send output (for this reason, as well as allowing third-party light wallet servers to trim the key image set and give the clients access to their outgoing transactions). If you have 2 normal outputs to the same destination, and need at least one self-send, that means you wouldn't be doing the shared xK_e optimization.

@jeffro256
Copy link

Also, having K_o bound to the tx output index would be really annoying (AKA involve brute-forcing private ephemeral keys) since IIRC the enotes in Seraphis are ordered by one-time addresses.

@tevador
Copy link
Author

tevador commented Sep 27, 2023

The protocol doesn't allow it though. One of the rules of Jamtis is that every transaction contains at least one self-send output

How is this rule enforced?

Imagine the following scenario:

Mallory registers at an exchange and is provided with a deposit address. She crafts a 2-output transaction without change, sending both outputs to the deposit address, each output worth 1000 XMR. In order to do this, she needs to provide inputs with a total sum of exactly 2000 XMR + fee, but that should not be hard to do.

Unless the exchange has a wallet that is aware of the burning bug, Mallory will be credited with 2000 XMR and can proceed to withdraw the funds back to her custody. However, the exchange will later realize that only one of the 1000 XMR outputs can be spent. This scam can be repeated until the wallet of the exchange is completely drained. It only costs some tx fees.

Relying on all wallet implementations to be able to detect this bug is not going to work, so there are basically two solutions:

  1. Mandating unique K_o within each transaction as a consensus rule.
  2. Including the output index when deriving K_o.

IIRC the enotes in Seraphis are ordered by one-time addresses

Is this a consensus rule or just a recommendation for tx builders?

@jeffro256
Copy link

jeffro256 commented Sep 27, 2023

How is this rule enforced?

That rule specifically is not enforced at a consensus level, it's just a Jamtis rule-of-thumb that is derived from the Seraphis protocol consensus rule that enote outputs within a transaction should be ordered and unique by one-time address. See this code for details and implementation: https://github.com/UkoeHB/monero/blob/eeca802ccee217d26acd8bc89ee69bbd3c47e254/src/seraphis_main/tx_validators.cpp#L365-L367.

In this way, and assuming that input_context differs from transaction to transaction, all cases of the burning bug should be covered.

@tevador
Copy link
Author

tevador commented Sep 27, 2023

consensus rule that enote outputs within a transaction should be ordered and unique by one-time address

Cool. It's the first time I hear about this rule. Maybe it's worth adding it to the Seraphis specs?

The current "Implementing Seraphis" paper says the following:

To further ensure uniqueness within a transaction, transaction verifiers must mandate that all values K_e in a transaction are unique.

Uniqueness of K_e is not sufficient to prevent the burning bug as shown above.

@jeffro256
Copy link

Uniqueness of K_e is not sufficient to prevent the burning bug as shown above.

That's true and a good thing to point out more explicitly in the spec. I can open an issue on that repo to clarify that passage.

@jeffro256
Copy link

jeffro256 commented Sep 28, 2023

This brings me to an interesting privacy hiccup when distributing xk_fr to a third-party, under both the new and old schemes: depending on the size of the previous view tag / primary view tag, a third-party will see that outgoing transaction enotes are exponentially more likely to to be owned by a user the more self-send enotes there are. This affects both light wallets and people using the payment validator tier.

We can reason that the number of successful view tag checks within a transaction unrelated to you follows a binomial distribution. Each view tag check is a Bernoulli trial, so we can expect the number of successful view tag checks X for a transaction with n outputs to follow the distribution X ~ B(n, VTFP), where VTFP is the view tag false positive rate. The probability mass function for getting k view tag matches can be written as P(X = k) = (n choose k) * VTFP^k * (1 - VTFP)^(n-k). As an extreme example, someone may implement PocketChange-like feature which breaks up outputs to help users work around the 10-block lock. Let's say they create 16 self-send outputs and the false positive rate is 1/256. All 16 outputs will be matched by view tag, which should normally only have a probability of 2.939 x 10^-39 (the same chance as randomly guessing someone's AES key). This can also happen with 2-output transactions with one self-spend and one change, although not as severe: the probability should be 1/65536.

We need a way to have third-parties scan the information they need without this privacy downside. I propose that we split up the self-send types into three self-send types: SELF_SPEND, PLAIN_CHANGE, & AUXILIARY_CHANGE. When doing an outgoing transaction, enote types SELF_SPEND XOR PLAIN_CHANGE (one or the other, not both) will always be present. For these enotes, primary view tags will calculated as normal. For any additional desired self-sends, we set the primary view tag to random bits and the self send type to AUXILLIARY_CHANGE, but do everything else the same (meaning binding the self-send type to s^sr_1). When it comes time to scan, we also scan all enotes in transactions in which any of the view tags matched even if their view tag did not match (hence "auxiliary"), but only scan them for type AUXILIARY_CHANGE. Unfortunately, this change will more than double the bandwidth required for light wallet clients, but only marginally affect compute time as no extra DH ops are required, and depending on the complementary view tag size, most enotes won't have to have K_o recomputed.

@jeffro256
Copy link

jeffro256 commented Sep 28, 2023

This wouldn't have to slow down non-auxiliary enote scanning at all (besides an extra amount commitment recomputation on an already confirmed owned enote) due to the following reason: since we assume that exactly one of SELF_SPEND or PLAIN_CHANGE is present in a transaction, they can share the same s^sr_1 derivation, and only have s^sr_2 derivation differ (this avoids the problem of sharing xK_e leading to the same K_o). The s^sr_1 derivation for AUXILIARY_CHANGE would differ, which leaves us with the same number of K_o re-computations that we have to do: 1x for plain check and 2x for self-send check.

For any additional desired self-sends, we set the primary view tag to random bits...

To speed up auxiliary enote scanning, we could actually fill the primary view tag bits up with all complementary view tag bits, since we don't care about it matching anyways, but we're also going to check the complementary view tag.

@tevador
Copy link
Author

tevador commented Sep 29, 2023

Responding here to a reddit comment.

My first major issue with them is that they break one of the big improvements promised by Seraphis, that being the ability to sign a transaction, let it sit, and then broadcast it later by creating the membership proof just before being shipped off.

Dynamic view tags behave exactly like dynamic fees in this regard. When signing a tx, you already commit to the chain state by selecting a fee amount. If the dynamic fees adjust upwards before the tx is submitted, you might have to mine the tx yourself as it won't be relayed. The same applies to dynamic view tags, with the minor difference that the tag size can adjust both upwards and downwards. The dynamic tag size will adjust very infrequently in typical situations. With current chain history, the last adjustment would have been about a year ago (chart). You can mitigate the risk for pre-signed transactions by signing two versions with the 2 most likely view tag sizes.

Second is that it makes Monero even more complex.

That's a non-argument. All new features make Monero even more complex. The question is if the complexity is worth the benefits it brings. For dynamic view tags, I think the answer is yes if we're already introducing an extra public key in every address just to support 3rd party scanning. If we don't adopt dynamic view tags, I think we should revert back to the original Jamtis design with 3 public keys as it seems like a better compromise between complexity and privacy with 3rd party scanning.

@kayabaNerve
Copy link

kayabaNerve commented Sep 29, 2023

@tevador I can't personally support dynamic view tags if dynamic view tags are part of the signed blob and requires re-signing to adjust the size of them. It's very different from creating a TX, saving a 2-byte view tag, publishing it as 1-byte, then rebroadcasting it as 2-byte if necessary, than re-signing entirely.

If they're not part of the signed blob, then they lose their integrity, as anyone can frontrun a TX with invalid view tags in the mempool.

Accordingly, I'm unable to voice support for this complexity due to the practical issues it'd cause (not just complexity which may cause practical issues).

I will also note that while I'm not up to date on JAMTIS, I leaned towards adding an extra key per @jeffro256. What I'd most like however is not to decide on whether or not to have an extra key, yet to have a complete spec document considered up-to-date (not with hundreds of errata comments) and final barring:

  1. Incredibly minor tweaks (DST choices, round counts)
  2. Major issues found. I would not call any issues in view tag tiers major unless they fundamentally invalidate the tier.

Though I'm sure this desire to be finite is well shared, meaning my statement of it may not contribute.

@tevador
Copy link
Author

tevador commented Sep 29, 2023

I want to reiterate my view that the proposed change to extend Jamtis addresses to 4 public keys to improve 3rd party scanning might be wasteful without dynamic view tags.

The static 8-bit view tag works well with a "medium" transaction volume, which is a range from mid tens of thousands to mid hundreds of thousands of enotes per day. We're presently near the bottom of this range. Within this range, the 8-bit view tag provides both good filtering and sufficient anonymity.

However, if a bear market hit and the tx volume plummeted for some reason, then there would be nearly no privacy advantage compared to the 3-key variant of Jamtis. Similarly, if Monero is successful and the tx volume goes up by 2 orders of magnitude, light wallets might be forced to switch to a less private wallet tier to reduce the bandwidth and computation costs. This would also remove any privacy advantage of the 4-key variant.

In both of these scenarios, 3rd party scanning will suffer a privacy loss and we'll be stuck with longer addresses and bloated specs.

Note that even the 3-key variant of Jamtis significantly improves 3rd party scanning. Currently, light wallet clients have to give up their private view key and leak practically all of their transaction history to the scanning server. With 3-key Jamtis, light wallet clients would only give up their "find-received" private key, which will reveal only some incoming transactions (e.g. recurring payments to the same address) without amounts to the scanning server.

A major advantage of 3-key Jamtis is that the 3rd party scanning improvements come "for free" because they are simply a byproduct of Janus attack protection provided by the 3rd public key, so even if 3rd party scanning doesn't catch on, we won't be wasting anything.

@kayabaNerve
Copy link

👍

I can't react to comments, apparently, so I'm forced to leave a new post. I hear you, that all sounds sane, and I have no further comments to contribute at this time.

@jeffro256
Copy link

jeffro256 commented Sep 29, 2023

I think what would solve all these issues is a arbitrary-size-by-concensus (with a reasonable limit, e.g. 24 bits) fixed-size-by-relay view tag. It's just as simple to implement because it does not depend on chain data. A cold signed tx won't temporally be invalidated unless you hold it so long that relay rules change (which is already an issue for fees). The view tag can be part of the signed blob without the need for multiple signings. We can adjust for really low and/or current tx volume if the anonymity set gets dangerously small. Conversely, if there is a large outcry from users that the bandwidth/computational requirements are unmanageable, we can manually increase the size (this really shouldn't happen more than a once every several years if at all, since we can assume that most user's machines / network connections will get at least slightly better year over year). Attackers cannot affect the view tag size by spamming the chain. All in all, we would reap the benefit of fixed-size view tags' linear increase in privacy with transaction volume and general robustness, but we could have a community handbrake if things got bad.

I think that we're all trying really hard to look ahead into the future and predict what tech trends will be like and what user's reactions to them will be and we could sit here all day postulating different user's different rationales for doing things, and create a decent solution for that specific use-case. Ideally, we want something that is both flexible and simple, and I think that making the view tags arbitrary size by consensus, but fixed size by relay is the best way to do that.

@expiredhotdog
Copy link

When signing a tx, you already commit to the chain state by selecting a fee amount

Sure, but that's not quite the same. You can always just set an aggressively high fee rate which practically guarantees that it'll work, whereas the view tag size would have a specific range enforced by consensus (I assume). Unless we take a different approach like @jeffro256 's proposal.

You can mitigate the risk for pre-signed transactions by signing two versions with the 2 most likely view tag sizes.

That seems like an incomplete solution, and isn't guaranteed to work in case of a large surge in volume, whether malicious or not.

The other option would be to have the tag itself signed normally, but its "accuracy level" bundled with the membership proof. That way you can overshoot the number of bits while constructing the transaction, but set its "claimed" accuracy immediately before broadcasting. The downside would be potentially allowing a 3rd party scanner to filter more accurately.

But it might not make much of a difference depending on how many extra bits you fill in: even 4 extras would be expected in 1/16 matches just by random chance, which isn't really that bad, considering how many matches there would already be per day. Especially since not all transactions will use this method.

Maybe this has been brought up before, and if so, then... whoops my bad.

@tevador
Copy link
Author

tevador commented Sep 29, 2023

view tag size would have a specific range enforced by consensus

No, the dynamic tag size would be a relay rule, just like fees.

isn't guaranteed to work in case of a large surge in volume

The view tag size is calculated based on the last 100 000 blocks. Even a large surge in tx volume will take weeks to affect the tag size. Note that during hard forks, we give old transactions only 24 hours to be confirmed before they are permanently invalidated (v9, v11, v14 and v16). The view tag adjustment would never permanently invalidate transactions, it would only make it harder, but not impossible, for them to be mined.

@kayabaNerve
Copy link

I'd be fine with a relay rule so long as on-change, the prior and new value are both valid for a period of 24 hours.

@expiredhotdog
Copy link

expiredhotdog commented Sep 30, 2023

No, the dynamic tag size would be a relay rule

Okay, so not a consensus rule. However it still would, effectively for most users, make it an unusable transaction.

we give old transactions only 24 hours to be confirmed

The 24 hour grace period isn't really an issue since it only happens once every few years and is known in advance, compared to (potentially) within weeks. I think this is a pretty significant tradeoff, potentially not worth the benefits.

Signing multiple different versions of the transaction does work, but it's very much a bandaid-type solution which we should try to avoid.

@tevador
Copy link
Author

tevador commented Sep 30, 2023

I'd be fine with a relay rule so long as on-change, the prior and new value are both valid for a period of 24 hours.

The current proposal has the following:

  1. 10-block delay between the calculation and the view tag size taking effect. This prevents short reorgs from reverting view tag size changes. Longer reorgs will already invalidate transactions due to decoys.
  2. 10000-block (2-week) grace period after each change when both the previous and the current tag sizes are relayed.

However it still would, effectively for most users, make it an unusable transaction.

There is a big difference between invalidated-by-consensus and invalidated-by-view-tag presigned transactions. The former one is useless as you can never hope for the old consensus rules to be restored. The latter can be mined in two situations:

  1. If tx volume changes back and the view tag size again matches the one used in the tx.
  2. The user solomines the tx themself. Due to the existence of hashpower rental services, this option is available to anyone and probably worth it if the presigned tx is valuable enough.

The 24 hour grace period isn't really an issue since it only happens once every few years and is known in advance

If a 24-hour grace period known 1-2 months in advance (e.g. the v11 fork date was finalized 25 days prior to the fork) to permanently invalidate presigned transactions is acceptable, then I think a grace period of a few weeks to temporarily invalidate presigned transactions is also fine.

@jeffro256
Copy link

jeffro256 commented Oct 31, 2023

Here's my attempt at distilling weeks of conversations into a nice table of different contentious Jamtis proposals crossed against properties, so others don't have to read all those comments.

Abbreviation Table

Abbreviation Term
XXK Extra Exchange Key
VT View tag
ATH Address Tag Hint
LW Light Wallet
LWS Light Wallet Server
BCR Bandwidth & Computation Requirements

Quick Summary Table of Contentious Diffie-Hellman-Related Jamtis Proposals

Current XXK XXK + 2 fixed-size VTs - ATH XXK + "dynamic" VT - ATH XXK + "flexible" VT - ATH
Public Address Size 196 247 244 244 244
Fixes Nominal Address Tag Privacy Issues?
Can do Delegated Public Address Generation?
Transaction Size Change None None None +1 byte/tx +1 byte/tx
Doesn't Need Recent Chaindata for VT constr.?
Balances LW BCR Automatically?
Community can change LW BCR w/o fork?
LW Anon Set (Against LWS) Increases w/ Volume?
Scan Speed Change (w/ 8-bit primary VT) 0% -0.4% -0.4% -0.4% -0.4%
Post-Primary VT CPU Time Required For Scanning 1x 100x 100x 100x 100x

What the Rows Mean

  • Public Address Size - The character count for the human-readable Jamtis address you give to sender
  • Fixes Nominal Address Tag Privacy Issues? - Under this scheme are you protected against:
    • A light wallet server identifying incoming enotes with 100% accuracy if they know your public address
    • A light wallet server identifying incoming enotes with 100% accuracy if the public address is sent to more than once
  • Can do Delegated Public Address Generation? - Can a third-party generate addresses on your behalf without any additional loss of privacy?
  • Transaction Size Change - self-explanatory
  • Doesn't Need Recent Chaindata for VT constr.? - Are transaction constructors free from needing up-to-date chain info specifically for constructing view tags?
  • Balances LW BCR Automatically - Does the amount of enotes matched by a LWS stay relatively constant over long time periods so LW BCRs don't increase over time?
  • Community can change LW BCR w/o fork? - Can the community manually change the view tag match rate to meet current user demands?
  • LW Anon Set (Against LWS) Increases w/ Volume? - Does the set of transactions per time period that the LWS knows your wallet is limited to increase with transaction volume?
  • Post-Primary VT CPU Time Required For Scanning - Compared to current Jamtis, the CPU time required to scan for incoming enotes after performing the primary view tag check is 100 times more. This is because each Twofish decipher op is replaced by a x25519 scalar multiplication op. This means that light wallet clients will do ~100 times more CPU work than before, but the bandwidth needed remains the same.

Summary of "Auxiliary" Enotes

Refer to this comment for the auxiliary enote proposal. This change can apply to any of the above proposals, including current Jamtis. Basically, when a transaction has too many self-send enotes, that fingerprints that tx as owned by a LWS. The proposal would allow LW users to churn and to create pocket change without any additional loss of privacy. The downsides is that if any enote matches a primary view tag, all other enotes in the transaction must be attempted to be scanned against the AUXILIARY_CHANGE enote type. For full wallets, this only entails a 1+ extra hash operation slowdown every time an enote matches a primary view tag, but for light wallets, these extra enotes must ALSO be sent "over the wire", also increasing bandwidth requirements.

Difference between "Dynamic" and "Flexible" View Tags

On-chain, both will be serialized as a fixed size buffer of 3 (optionally 2?) bytes per enote. Additionally, there is one integer per transaction, called npbits, constrained to range 0-24 (16 if view tag buffer only 2 bytes wide), which encodes the number of bits from the front of the buffer used to match the primary view tag. Likewise, ncbits is the number of bits from the back of the buffer used to match the complementary view tag, and it is calculated as ncbits = 24 - npbits. The difference between "dynamic" and "flexible" is how the value npbits is enforced. Under the "flexible" scheme, the value npbits is set to a constant value, enforced by relay rule. Under the "dynamic" scheme, the value npbits is enforced to be a function of the on-chain transaction volume. See this comment for the exact proposed formula. Neither of these schemes cause uniformity issues because at transaction construction time, there is only one correct value to choose for npbits. What's nice about the flexible vs dynamic debate is that, as long as npbits is enforced only by relay rule, the community can switch back and forth between the two proposals as it sees fit without forking.

"Less-Contentious" Shared XXK Tweaks

Here's some extra details about tweaks accumulated through discussion that are shared for all XXK proposals and aren't hotly debated, but might be still worth a mention:

  • Suggested by @tevador
    • Get rid of unlock-amounts key in order to help thwart identify-involved fake tier
    • Make one DH privkey depend upon other in secrets derivation tree for same reason
  • Suggested by @jeffro256
    • Bind second/complementary view tag to "residue" of primary DHE in order to help thwart identify-involved fake tier
    • Bind the amount baked key to the first Diffie-Helman exchange to prevent probabilistic Janus attack

@tevador anything I'm missing?

@Gingeropolous
Copy link

so does column XXK still have a -0.4% scan speed change, even though the column name doesn't include VT?

@jeffro256
Copy link

jeffro256 commented Nov 1, 2023 via email

@Gingeropolous
Copy link

So then what is the point of viewtags? With the whole auxiliary enotes thing, it seems that viewtags have a critical flaw that is being hacked around by auxiliary enotes, which seems like added complexity for no gain if the XXK column has the same -0.4% scan speed change without VT.

@j-berman
Copy link

j-berman commented Nov 8, 2023

View tags

I think a flexible view tag is a reasonable option.

  • Relayers start by enforcing a 1 byte view tag.
  • Consensus allows dynamic view tags.
  • If volume starts to increase significantly and sustainably AND light wallets are widely used AND the UX for light wallets starts to degrade AND the use case of submitting presigned txs many weeks in advance ends up an edge case that is outweighed by the light wallet use case, then relayers start enforcing a dynamic view tag.

A major advantage of 3-key Jamtis is that the 3rd party scanning improvements come "for free" because they are simply a byproduct of Janus attack protection provided by the 3rd public key, so even if 3rd party scanning doesn't catch on, we won't be wasting anything.

I think adding a 4th key to the address is still worth it, even if a dynamic view tag is never implemented, because I would rather users be in a situation where an extra key ends up wasted as opposed to a situation where their privacy is worse than it could otherwise be.

On auxiliary enotes

I think the benefits of this are worth it especially with full chain membership proofs. Pocket change is a use case people seem to clearly want (even despite its privacy issues today), to the point where I can see it being a reasonable default wallet behavior with full chain membership proofs.

So then what is the point of viewtags?

@gingeropouls in the current Jamtis spec (and in this matrix of proposals), its primary value-add is offloading the bulk of scanning to a server while preventing the server from being able to know all enotes the user received and spent with cryptographic certainty. It also speeds up full wallet scanning basically the same as view tags do today (i.e. when you don't give up a key to the server).

As currently proposed, if you send yourself e.g. 9 enotes in a tx (e.g. if you were to use a pocket change-like feature), and you give your "find-received" key to a server that can only identify view tag matches of txs which may belong to you, then the server could see "hey, this user had 9 view tag matches in this tx which is statistically very unlikely, therefore the user almost certainly received all 9 enotes." The auxiliary enote proposal above is strictly to ensure even pocket change-like txs would only have a single view tag match among all 9 enotes, so the server would still identify the tx as one that the user needs to scan all enotes for.

@jeffro256
Copy link

Two points about Janus attacks under the new proposed scheme:

  1. It is possible to do a Janus attack on any address when the attacker knows any one address private key. Let's say the attacker knows address private key kja and the Jamtis address corresponding to that address private key. This address private key may be revealed, for example, during an address index proof. The attacker generates ephemeral pubkey Ke = r Kibase (i is index of a new dest to be attacked). The attacker uses the ephemeral key based on an address index i, but actually encrypts the addr_tagj from the old Jamtis address j. We then do the rest of enote building from the address j. When it comes to make the amount baked key, the receiver will calculate kja * Ke. The attacker will know what this value is supposed to be since he knows kja. He can then calculate the "correct" amount baked key, by doing kja * Ke instead of r * G, and therefore, calculate the "correct" ssr2. We can fix this by including a factor of our account secrets in our base key (e.g. the view-received key). This is the reason this attack doesn't work on Jamtis currently: Kj3 contains a factor of kua, which the attacker wouldn't know.
  2. Including Kdaf in the hash of the amount baked key doesn't actually do anything to prevent Janus attacks, since the attacker knows what it should be. Also it isn't needed as long as we can prove that the ephemeral key is "bound" to a certain address index j, since otherwise, we won't reach the same shared secret.

@jeffro256
Copy link

This might seem like a trivial change, but I suggest that we remove the 'a' character from the address header. Two reasons: 1) we make addresses one character shorter (duh), but second is more important: 2) this prefix scheme falls in line with Bech32, litecoin, etc where you have characters in the ticker followed by a version number. I can see confusion possibly arising when people generate an address with the letter 'a' inside the prefix: "Huh? What is XMRA? I don't want XMRA, I want XMR!" initiate frustration

@rbrunner7
Copy link

I suggest that we remove the 'a' character from the address header.

After some searching I found out what that "a" stands for in the first place, see this comment: The letter there has two possible values, "a" for "anonymous address" and "c" for "certified address". There was quite some discussion here over many comments whether such certified addresses are a good idea, ok but overkill, or even a bad idea; I did not go through it all. But I am sure the decision to remove that "a" amounts to deciding whether we support this address distinction in the proposed form, or at least will in the future, with more extensive tooling.

If I was to decide alone I would probably let that stand without much further ado and research ...

@kayabaNerve
Copy link

I don't believe it'd be an issue to have xmr1 vs xmrcert1 and prefer xmr alone.

I also don't believe we should have multiple distinct address in general (though I'd have to double check the certified address discussion). If there's no active plans to support certified addresses now, I'd remove "a".

@tevador
Copy link
Author

tevador commented Apr 3, 2024

The Jamtis invoice encoding starts with the prefix xmri. The Jamtis Wallet keys encoding starts with the prefix xmrw. Other encodings are possible in the future.

The address prefix xmra clearly distinguishes addresses from the other encodings. It might be possible to have just xmr if we avoid using i and w as the version character (which would be beyond version 10, so unlikely to be needed anyways).

no active plans to support certified addresses

Certified addresses have been superseded by certified invoices.

@jeffro256
Copy link

jeffro256 commented May 22, 2024

Discussing post-quantum secrecy for incoming enotes spends on Jamtis-RCT here got me thinking about the same issue with Jamtis on Seraphis. For a few days, I was convinced that Jamtis-Seraphis didn't have this problem because of the different linking tag format, and the fact that the y/x in the linking tag makes it so that there are 2 "unknowns" if a DLP solver knows l = dlog(L, J) = y/x (this is opposed to 1 unknown in Jamtis-RCT L = x Hp(O). However, with just one public address, its address index extensions, and a public linking tag for an enote spending from that address (plain, non-selfsend), a DLP solver can solve for the view-balance key. Here's how it works:

How a DLP Solver Can Find the View-balance key with Address Index Extensions and a Linking Tag

Notation

  • Fixed generators G, X, U, J
  • kvb: view-balance key
  • km: master spend key
  • dvr: view-received key
  • Sj: public address spendkey
  • S: base account spendkey
  • O: Enote onetime address
  • C: Enote amount commitment
  • L: Linking tag for enote
  • kg/x/uaddr: address index extensions keys for Sj
  • kg/x/uo: onetime extensions keys for enote
  • kg/x/uview: enote view extension keys for enote
  • s1sr: sender-receiver secret 1 for enote
  • dlog(P, G): a discrete log oracle/algorithm that returns p in P = p * G

Initial Knowledge

The DLP solver must know the address index extensions kg/x/uaddr for a public address with spendkey Sj beforehand. This is not normally public information, but may be exposed to a verifier during an address index proof or to a generate-address tiered wallet. By virtue of being a DLP solver, they will also be able to calculate dvr from a public address. This means that for any incoming (plain, non-selfsend) enote to that public address, they can calculate the onetime extensions kg/x/uo = H(S, s1sr, C). This means that they know the "enote view" extensions keys kg/x/uview = kg/x/uaddr + kg/x/uo. The other piece of public information that a DLP solver must know is the linking tag L that spends an incoming plain enote to that public address. So in total, the attacker must know (Sj, kgaddr, kxaddr, kuaddr, L) beforehand.

Masker Key Equation 1

Since we know the public address spendkey Sj and the address index extension keys kg/x/uaddr, we can find the base account spendkey S = Sj - kgaddr G - kxaddr X - kuaddr U. We now know the account base spendkey. We know that the account holder would calculate the account base spendkey as S = kvb X + km U. As a DLP solver, we would be able to find u = dlog(U, X), and thus know that S = (kvb + km * u) X. Again as a DLP solver, we would be able to find s = dlog(S, X) = kvb + km * u. Solving for km, we get our first equation in terms of km:

km = (s - kvb) / u

Master Key Equation 2

Onetime addresses are constructed O = (kvb + kxview) X + (km + kuview) U + kgview G, which means that linking tags are constructed as L = (km + kuview) / (kvb + kxview) * J. Given a linking tag L, a DLP solver can calculate l = dlog(L, J) = (km + kuview) / (kvb + kxview). Solving again for km, we can re-arrange this as:

km = (kvb + kxview) * l - kuview

Solving

We now have two equations with two unknowns: km and kvb. We know all the other values in the equations: s, u, l, kxview, kuview. Using basic algebra, we substitute km:

(s - kvb) / u = (kvb + kxview) * l - kuview

and solve for kvb:

kvb = (kuview + s/u - kxview * l) / (1/u + 1/l)

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