2 · The SAL (Spend Authorization + Linkability)

Warming up

First let's remember what an enote is. In the CLSAG era, when we send a transaction to someone, we create an unspent output (enote), which has the format of an one-time address O together with an amount commitment C:

O=Hs(r·PVi)·G+PS(1)C=Hs(commitment_maskHs(r·PVi))·G+a·H(2)
termwhat it iswho knows it
PV, PS the receiver's public view and spend keys: PV=sv·G and PS=ss·G public (in the address)
ra fresh random secret the sender draws for this transaction. The matching R=r·G is published in the transaction sender
r·PVthe Diffie–Hellman shared secret: the sender computes r·PV, the receiver computes sv·R. The same point.sender and receiver only
ithe output's index in the transaction public
Hs(r·PVi)hashing the shared secret to a scalar: the one-time key offset. This is what makes P look unrelated to the receiver's address PS and why addresses aren't visible on the chain sender and receiver only
athe amount being sentsender and receiver only
Ha second generator with unknown discrete log w.r.t. G, so the amount is hidden and the commitments still add up (balance)public

To spend O, the receiver derives its private key x=Hs(sv·Ri)+ss (only he can do that, since it needs the secret view key sv and the secret spend key ss). Note that we could simply write the enote as a public point O, and one secret x with O=x·G.

Let's refresh what the key-image is now.

The key image (L)

Also named linking tag, because it links to an output (enote). The quantity of key-images on chain is the quantity of spent enotes. But we don't know which. An useful intermediate step before getting the key-image is the definition of I, which is just the hash to point (Hp) of O. Everybody can create I since all the O, are public:

I=Hp(O)

The key image is just the point above (that nobody knows the secret of), multiplied by the same secret x that opens the enote:

L=x·I=x·Hp(O)

Keep these two definitions because they will be with us for the rest of the page: I is derived from the enote alone (anyone who sees O can compute it), while L also needs the secret x (only the owner can compute it). Notice that FCMP does not change the definition of the key image, it is the same as it always was in Monero.

So far, we are just warming up, nothing new. So now let's introduce the idea of ownership (which represents the '++' in the acronym FCMP++). We want to prove that someone who holds the secrets (private keys) of an enote (output) is able to spend it. But before doing that, let's talk about a new concept that FCMP++ introduces: the separation of the ownership proof from the membership proof (so far, with ring signatures like CLSAG and MLSAG, we were doing both at the same time)

Ownership proof

There is a lot to talk here so first let's pause for a second and think about one implication of separating the ownership proof from the membership proofs: transaction chaining . As a motivation, see the example below to see how it could work in practice.

Hypothetical scenario of transaction chaining

Alice and Bob decide to fund a charity together and set up a 2-of-2 multisig wallet, so every transaction needs both signatures. Alice puts in 100 XMR and Bob puts in 100 XMR. Neither can touch the 200 XMR alone so both have skin in the game, and by funding the wallet each proved to the other that he was serious.

Then they sit down to choose a cause and discover they don't agree. Alice wants project A but Bob wants project B. Now the wallet that was supposed to bind them together has trapped them instead. Alice cannot get her 100 XMR back without Bob's signature, and Bob cannot get his back without hers. They are not acting like partners anymore and they become each other's hostages, and every argument about the charity is really an argument about a ransom. Or even worse still, suppose Bob simply loses interest and walks away, maybe 100 XMR is not much to him and he stops answering. Alice's money is now stuck.

Transaction chaining kills the vanishing-partner problem. Before either of them funds anything, while they are still friendly and cooperative, Alice and Bob jointly sign one more transaction, the refund, which spends the enotes they are about to create and pays 100 XMR back to each of them. Those enotes do not exist yet, but that is no obstacle because they know how it would look like and they could already sign a spend of them. And since the membership proof carries no secret, it can be built later by whoever ends up broadcasting. So they sign it but they do not relay it, and each keeps a copy in a drawer. Only then they do fund the wallet.

Now Bob's disappearance costs Alice nothing. She builds the membership proof herself, broadcasts, and her 100 XMR comes back to her without a word from him. And because he knows she can do this, he has no leverage to bully her into project B either. The hostage situation is then gone from both sides. If instead they do agree on a good project, they simply co-sign a transaction sending the 200 XMR (or whatever amount) to it. That spends the enotes, and the moment they do it, the refund sitting in the drawer is dead as its inputs are no longer available (as they already spent their 100 XMR enote).

Notice what the refund really is. It returns each person's own money in a world where nothing has happened yet. Nobody has shipped goods, delivered work, or relied on the coins staying locked. Chaining solves the problem of entering a joint account without going first. It does not, by itself, make the deal that follows trustless.

Alright, let's restate what we want to achieve here: I want to convince you that I own an enote (he is mine because I can convince you that I know the secret keys to open it) and I can build a valid key image from it. All of that without revealing which enote it is.

Do you see the issue and the ingenuity here? I will convince you that I own an enote, without telling you which (so it could be on chain or not!). So this is what gives the ability for transaction chaining. But for a valid transaction to happen, I have also to convince you that the enote I am spending from is really on chain (membership proof) and was never spent before (this part is easy because we know that we can't have duplicated key images and they should be uniquely built from the enotes).

So let's get started with the ownership proof.

Step 0 — the simplest Schnorr proof

Before going into the actual SAL proof done in FCMP, let's start with the most basic Schnorr proof (which is the foundation of the SAL proof). Suppose that I have a public point Q=x·G and I want to convince you that I know x without telling you it, obviously.

So first I draw a fresh random scalar k, called the nonce, and compute the point A=k·G. Then I hash it, together with everything else that is public, into a scalar (the challenge e):

e=Hs(QAtx)

next, I create the scalar s=k+e·x, which contains my secret key x but it is not possible to retrieve it just looking at s. The proof I publish is just the pair (A,s). Anyone can recompute e from the public data, and then check one equation:

s·G=?A+e·Q

The check is one line of algebra. If we expand the left side with the definitions:

s·G=(k+ex)·G=k·GA+e·(x·G)Q=A+e·Q

If the left side is the same as the right side, congratulations, the prover knows x as only him could have built an s in such a way that this equation balances.

Notice that on a blockchain there is nobody available to play the challenger. A transaction is broadcast once and verified by strangers later. So we let the hash be the challenger. This is the Fiat–Shamir trick, and everything below rests on one property of it: e is a hash of A, so I must fix A before I can even know what e will be. The hash is the referee that nobody has to trust, precisely because nobody can predict it.

Why can't I cheat? Suppose I don't know x. Looking at the verification equation, there is an easy way to satisfy it: pick the answer s first, pick a challenge e, and then define A:=s·Ge·Q. The equation now holds by construction. I have forged a proof of a secret I don't have. But look at what that forgery required: I chose A after e. With Fiat–Shamir the order matters because e=Hs(QAtx) cannot be computed until A already exists. To cheat I would have to find an A whose own hash produces the very e I used to build it, which would mean finding a solution for the inverse of the hash (which is not possible). So the only route left is the honest one: commit to A and accept whatever e falls out. That is what "proof of knowledge" means.

Notice that the transaction itself can go into the hash, so changing one byte of it changes e and the proof collapses. The proof of knowledge could therefore be welded to a specific transaction and it could become a signature. This is precisely how Schnorr signatures work, and it is what the 'Spend Authorization' means.

Step 1 — two secrets instead of one

Back to Monero. Our FCMP++ enote is not only Q=x·G but it carries a second generator:

O=x·G+yo·T

The second generator is primarily introduced to precisely give us the opportunity to re-randomize it. In other words, to hide it since we don't want to reveal which output the prover is spending when he is creating the ownership proof.

So what happens to the Schnorr proofs? Nothing. It extends to as many secrets as we like. Commit with one nonce per secret, A=kx·G+ky·T, respond with sx=kx+ex and sy=ky+eyo and check:

sx·G+sy·T=?A+e·O

The same expansion as before goes through term by term. Remember this shape, almost every equation in the rest of this page is exactly this, with different letters.

Step 2 — the obstacle: we don't want to show O

The proof above talks about O: the verifier needs O to check the equation. But if we show O, then everybody will know immediately which is the enote we are spending! We don't want to do that so we publish a re-randomized copy, which means the enote plus a fresh random multiple of T, the generator we reserved for exactly this:

O~=O+ro·T=x·G+(yo+ro)y·T

And that is the beautiful part: O~ has the same shape as O. It is still "x on G, something on T", only the something changed. So the step-1 proof works unmodified on O~, with the enote's own yo and the rerandomizer ro folded into a single secret y=yo+ro. From here on, y always means that folded scalar, which is the only form the proof ever needs:

O~=x·G+y·T(equation 1)

Two properties make this disguise work, and they are the two properties every commitment needs.

Hiding. ro is uniformly random, so ro·T is a uniformly random point, so O~ is a uniformly random point — whatever O is inside.

Binding. Could I later claim O~ hides a different enote O? I would need O+ro·T=O+r·T, that is OO=(rro)·T, which means the discrete log between real enote keys and T. Since T is a NUMS (nothing-up-my-sleeve) point, no such scalar is known to anyone.

Step 3 — the key image must be properly formed

Equation 1 intends to prove that I know the spend keys behind O~. It says nothing about the key image L=x·I and without that link I could publish a fresh key image for an already-spent enote and double-spend at will. We must prove that L uses the same x and the right I.

But I=Hp(O) is computed from O. So it must be re-randomized too, on its own generator U:

I~=I+ri·U

Now try to state the key image in terms of what the verifier can see. Multiply the disguise by x:

x·I~=x·I+xri·U=L+xri·U

Rearranged, we arrive at a statement about public points only (notice that the key-image is a public point. It reveals nothing about the enote spent as it is impossible to go from the key-image to the enote):

L=x·I~z·U(equation 2)wherez=xri

Equation 2 looks like a victory: the verifier can read every symbol in it. L is published, I~ is published, U is a fixed generator, and the two unknowns x and z sit on their points linearly, exactly the shape a Schnorr proof needs. But look closely at what z is supposed to be:

z=x·ri

It is a product of two secrets and that is a problem because Schnorr does not know how to handle products. Its entire machinery is the response s=k+e·secret, which can only ever pin down a secret sitting alone as a coefficient of a public point. Nothing in that equation can express "and by the way, this scalar happens to be those two other scalars multiplied together". So when we hand equation 2 to Schnorr, it does not see x·ri. It sees a symbol z, and it treats it as one more independent secret, entirely free to be whatever the prover wants.

And that freedom could be fatal. Watch how a cheater could exploit it. He owns the enote, so he genuinely knows x, no need to forge that part. He simply picks a random z of his own, unrelated to any ri, and then defines his key image by the equation itself:

L:=x·I~z·U

Equation 2 now holds perfectly, he built L so that it would. He knows x, he knows z, and both appear linearly, so he can produce a flawless Schnorr proof of it. Yet L is not the key image of his enote. Worse, he can do this again tomorrow with a different z, getting a different L . So one enote generating an unlimited supply of valid-looking key images and that is the end of Monero! Obviously that is not what is happening as we know that one enote is supposed to only generate exactly one key image.

So the last obstacle is this: we must force z to be the genuine product x·ri, and we must do it using only linear equations, because linear equations are all our proof system knows how to check.

Step 4 — pinning the product: the points R and P

We need to force z to be the genuine product xri. Two points will do the job here.

The first is R. It is published alongside O~ and I~, and it commits to the very ri that disguised I~, on its own generator V (with a fresh blind rri, so it hides ri):

R=ri·V+rri·T

The second is P. This one carries both factors and their product at once, each on its own generator (with a fresh blind rp on T):

P=x·G+ri·V+z·U+rp·T

Now two things must be shown about P, and together they are the whole trick.

First: the ingredients of P are the same secrets as everywhere else. A cheater would love to build P out of a fresh x and a fresh ri unrelated to the enote he is spending. But watch what happens when we subtract O~ and R from it: the x·G cancels against equation 1, the ri·V cancels against R, and what survives is

PO~R=z·U+(rpyrri)·T(equation 3)

The left side is nothing but public points. The right side is linear in the secrets. And the cancellation only happens if the G-coefficient really is the x inside O~ and its V-coefficient really is the ri inside R. Otherwise a leftover G or V term survives, and the equation cannot balance. So P is welded to the same x and the same ri we have been using all along, and the scalar sitting on U is exactly the z of equation 2.

Second: the U-coefficient really is the product of the G- and V-coefficients, i.e. z=xri. This is the part plain Schnorr cannot do, and the idea behind it is worth stating before showing the formulas.

The verifier never sees x or ri. All he gets are the two Schnorr responses, sα=α+ex and sβ=β+eri, each a factor blended with its own nonce. The one natural thing he can do with two numbers is multiply them, so let him. Watch what xri does when he multiplies the responses:

sαsβ=(α+ex)(β+eri)=αβe0+(αri+βx)e1e+xrie2e2

The product we care about (xri) appears in the e2 coefficient, followed by two lower-order terms: the nonces product (αβ at e0 ) and the cross terms (αri+βx at e1). If the verifier could subtract those two terms away, what remained would be exactly e2xri, and he could compare it against e2z to test whether z=xri.

That subtraction is the entire job of A and B. The prover commits, ahead of the challenge, to precisely those two lower-order terms: B carries the e0 term (the nonce-product αβ), and A carries the e1 term (the cross terms), each padded with a fresh T-blind so it hides its contents:

A=α·G+β·V+(αri+βx)e1 term·U+δ·TB=(αβ)e0 term·U+μ·T

Then we hand each of the three commitments a weight. We multiply B by 1, A by e and P by e2. Next we add them into one point e2P+eA+B, and check it against the response side. That is the whole verification:

e2·P+e·A+B=?esα·G+esβ·V+(sαsβ)·U+sδ·T(equation 4)

It looks dense, but lets break it in four smaller equations (for each generator). Let us expand both sides and read them off generator by generator. Start with the left, substituting the definitions of P, A, B and collecting each generator's coefficient:

e2P+eA+B=(e2x+eα)G+(e2ri+eβ)V+(e2z+e(αri+βx)+αβ)U+(e2rp+eδ+μ)T

Now the right. Each response is a blend of a nonce and a secret: sα=α+ex, sβ=β+eri, sδ=μ+eδ+e2rp (notice that sδ is defined before hand like that, so the equation balances only if z=xri) so substitute those in and expand, keeping one generator per line:

esαG+esβV+(sαsβ)U+sδT=(eα+e2x)G+(eβ+e2ri)V+(αβ+e(αri+βx)+e2xri)U+(μ+eδ+e2rp)T

Now lay the two expansions side by side, one generator at a time, and see what each row demands:

leftrightG:e2x+eα=e2x+eα alwaysV:e2ri+eβ=e2ri+eβ alwaysT:e2rp+eδ+μ=e2rp+eδ+μ alwaysU:e2z+e(αri+βx)+αβ=?e2xri+e(αri+βx)+αβz=xri

Three of the four rows are identities: G, V and T match term for term, for any values at all, because both sides were assembled from the very same responses, confirming sα,sβ,sδ are honest blends and nothing was tampered with. They tell us nothing about the product and they simply cannot fail.

The interesting part happens on the U row. There the two sides again agree at e0 (both αβ) and at e1 (both αri+βx). That agreement is exactly what A and B were placed on U to manufacture. So those two levels cancel, and after cancellation the entire equation has collapsed to its top level alone:

e2z=?e2xriz=xri

The whole apparatus of P, A, B and the squared challenge existed to bring the proof to this one line. The equation holds if and only if the scalar the prover put on U is the genuine product of his two factors.

The challenge is squared because the product under test sits at the e2 level, and A, B cancel the e0 and e1 levels so that it stands alone. This is what exposes a false product. Subtract the two U-rows:

Δ(e)=left|Uright|U=(zxri)e2+(lower-order terms)

The e2 coefficient is zxri. If the prover is honest, z=xri, this coefficient and every other vanish, Δ0, and the identity holds for all e. If zxri, the coefficient is non-zero and cannot be removed: z sits on U, the product xri is fixed by the x on G and the ri on V, and G,V,U are independent, so a term on one generator cannot offset a term on another. Raising the degree of the fake answer also does not help as it would leave Δ a non-zero value.

Let's look how the whole process looks now.

The SAL proof assembled

Collecting the boxed statements, SAL proves knowledge of four secrets that open the input, x, y, ri, rri, together with the product z=xri and the blind rp that hides P:

(1)O~=x·G+y·Tenote re-randomization(2)L=x·I~z·Uthe key image matches I~(3)PO~R=z·U+(rpyrri)·TP holds the same x, ri(4)and z=xrithe product is genuine

The first three lines are linear in the secrets, so each is a Schnorr proof of exactly the step-0 shape, only with more letters. The fourth is the product argument of step 4. And they all run together, sharing a single challenge e, which is what stitches them into one statement: the same x is forced through (1), (2) and (3), and the same z through (2), (3) and (4). Notice that four separate proofs with four separate challenges would each be true on its own and prove nothing jointly if they didn't share the same challenge e.

Nonces: fresh random scalars: α for x, β for ri, ry for y, rz for z, and δ,μ,rrp for the blinds.

Commitments: Replay each equation with the nonces in place of the secrets. The same idea as in step 0: once per line, plus A and B, which carry the cross terms of the product argument:

A=α·G+β·V+(αri+βx)·U+δ·TB=(αβ)·U+μ·TRO=α·G+ry·TRP=rz·U+rrp·TRL=α·I~rz·U

Challenge: Now we hash everything that is public: the commitments just fixed, the public points of the statement and the transaction being signed:

e=Hs(txO~I~C~RLPABRORPRL)

This single line is doing three jobs at once. It is the challenger, so no interaction is needed and the proof can be broadcast as a static blob of bytes. It arrives after the commitments, since it is a hash of them, so I cannot run the step-0 forgery of picking the answer first. And because the transaction went into the hash, the proof is welded to that transaction.

Responses: the usual blend, s=nonce+e·secret, six times with two of them carrying an extra term, because sδ has to absorb the blind of P at the squared challenge, and srp answers for the combination rpyrri that survived in equation 3:

sα=α+exsy=ry+eysβ=β+erisz=rz+ezsδ=μ+eδ+e2rpsrp=rrp+e(rpyrri)

The proof I publish are the six points P,A,B,RO,RP,RL and the six scalars sα,sβ,sδ,sy,sz,srp. Twelve 32-byte words which is equal to 384 bytes.

Verification: The verifier is someone reading the chain. He has the transaction, so he recomputes e himself with the same hash. Then it is exactly the step-0 check, once per equation: replay each equation with the responses, and see the commitment plus e times the public point come out.

RO+e·O~=?sα·G+sy·T(1) spend authorizationRL+e·L=?sα·I~sz·U(2) the key imageRP+e·(PO~R)=?sz·U+srp·T(3) P holds the same x, rie2·P+e·A+B=?esα·G+esβ·V+(sαsβ)·U+sδ·T(4) z=xri
Why each check convinces you

Take the second. Substitute the responses and use L=x·I~z·U:

sα·I~sz·U=(α+ex)·I~(rz+ez)·U=α·I~rz·URL+e·(x·I~z·U)L

It lands on RL+e·L only if x·I~z·U really is the published L. And RL is inside the hash that produced e, so it was frozen before e could be known. To fix a wrong L after seeing e, the forger would have to go back and change RL, which changes e again. That circle is the hash refusing to be inverted.

The first and third expand identically. The fourth is the product argument of step 4, where the same substitution makes the U row factor into sαsβ.

What lands on the chain

To make it more concrete, here are the values of a real transaction stored on testnet:

vin[i].k_image
  L      80e251c490c65c224a758c2caab46291ffe5d994232079ea37e3806ef2876584

fcmp_pp   (this input's 480-byte block)
  Õ      f71b2aa43485e2f1311d9ca29fda2af06d17f0d2dee91ea1e924f2f174f5fe50
  Ĩ      51448a0ccf621d52939d34b74e6b4c0f4ac3edad30645eba0323f4533fb0b650
  R      35c61ff53e702b2be7caed908739db92d13da85e87e1acfddf4ddc622d310476
  P      1924f4e4cfc5533081bdfc1d8eee24a62f40e61ccbed2705700d2ce3abf1ac30
  A      0ac37f5eb3d5f60616e286316160f50f477d48f715cc7e5f6bad3219945537c8
  B      dd1286b09cbf6e88077112f04d3e74741c3270089887b21c931544ddfc118c30
  R_O    7b9f1acbc068ea4cd40f61c0632137968c066054080888e310b7c5a8f39c24a7
  R_P    9a2871b0b1e37221ac5fe4503e68742bec42d3b3808299f86d7f18432f41d7fc
  R_L    6e930a718752e86f80c52e9282ab782b8e5c1ab51b6e6cc0a280fdf3a2607ddd
  s_α    5d6fc8d36a1832c4b2affc4fd420ca25361f346fff18549e6364448a6338b50c
  s_β    a54b6199c6634f5b78d55ec101d6f2e59168d08d4f275d70205a4fc4938e1e01
  s_δ    b015b0d4667cb20dfe63032644a40957ae4ee675c9240be10c579416ee10af01
  s_y    5b930f6189a8f28b7941cdce67e4009865e869ebe68da7d863970e3805a6880b
  s_z    c40b21d9bcc406fa5f4422aac31f0254205500c214ed5fab3d55b491dcd46b0b
  s_rp   1c76474565672b5464eb971e5d5b751eb0895cd69d8a86a8ad1a4b24c1199c0b

pseudoOuts[i]
  C̃      454e867009aef302815597139182d3b8123f57c3e09cd6ac724c4e7c9fe59d49

Now look closer at that block and ask a plain question: what did we actually prove we own? Every entry is a uniformly random point or scalar. O~ is our enote pushed behind a random multiple of T, I~ its hash-point behind a random multiple of U, and R and the twelve SAL words are blinded the same way. Nowhere in the block does the output O itself appear. Nor a clear hint to it.

And that is the gap. SAL proves I hold the secrets behind O~ and that L is the key image built from O with x, but nothing in it says O~ is the disguise of a real output O. I could run the identical proof over an O I invented this morning and pass all four checks.

So now we have ownership but we still owe membership: a proof that the output O hidden inside O~ is really on chain. Building that proof (over hundreds of millions of outputs and without revealing which) is what we attack next.