## Ring Signatures in the MLSAG era

Let's understand what are MLSAG (Multilayer Linkable Spontaneous Anonymous Group) signatures, how it differs from the Pre-RingCT and why the Monero developers implemented it on 05 January 2017. A simple description in layman's terms and a comprehensive explanation of the verification code is provided here.

#### MLSAG in Layman's terms

Before talking about MLSAG signatures, let's introduce some definitions and vocabulary to facilitate the understanding and motivation behind it.

Two innovations were introduced at this hardfork (v4):

1. A generalization of the ring signature scheme used before.
2. Let's add one more dimension into our visual understanding of the ring-signatures. Let's pick our ring from the Pre-RingCT era and add on top of it another ring that contains 'Differences of Commitments' $C$ as illustrated in figure 1. The exact meaning of C is explained also in the Amounts section. We can see now that instead of controlling just one private key, we will control one key vector, which is the pair [output, commitment] and we will generate a matrix (M-1)x2 of decoys, where M is the quantity of ring members.

3. RingCT (Ring Confidential Transactions): which hides the amounts transacted by setting it to zero (in the Pre-RingCT era, all the amounts are visible to anyone). You will find more details about it in the Amounts section. For now, let's try to understand how we hide the sender of the "0" XMR by understanding how this generalized version of the ring signatures work.

The biggest intention of the MLSAG signatures is to prove that one signer knows the private key of the entire key vector and as a result he owns the ouput being spent and the commitment amounts are valid. The proof alongside with the verification of a transaction is left to the next section.

Now you have an idea what MLSAG means:

• Multilayer: We have two rings. One with public keys and the other with commitment differences.
• Linkable: These two rings could be linked (but they are not) and the output key image can only appear once in the blockchain.
• Spontaneous: These rings are generated spontaneously by the users instead of having a centralized authority or a trusted setup to rely on.
• Anonymous: We cannot tell which one of the ring members signed the transaction.
• Group: It as group signature (one person from the group has to sign the transaction).

#### MLSAG Signatures in practice

Before understanding the generation and verification function of a MLSAG signature (generate_MLSAG(m,PK,sk,index) and verify_MLSAG(m,PK, I, $c_0$, ss)), we need to understand its inputs arguments and to what they relate to.

##### Message (m)

The message represents all the information of a transaction (version, unlock time, inputs, outputs, extra, rct_signatures and rct_prunable) except the ring signatures (MGs).

Here we have the message that the person is signing with his private key and it will also be verified using a verification function that proves that this message could only be signed by someone with the knowledge of a private key related to the published public key.

In the MLSAG era, the message is divided into three parts:

• P1 = version + unlock_time + vin + vout + extra
• P2 = rct_signatures
• P3 = rct_prunable

The final message $m$ is the hash of the sum of the hashes of each part, i.e. $m =\mathcal{H} (\mathcal{H}(P1) + \mathcal{H}(P2) +\mathcal{H}(P3))$

Let's get the transaction 2790982c9a44ae9516e395735c58f607d13741f8fd054bc096997aebc0a280d3 as example.

Here we have:

P1 = 020002020006e4ec6cccdd03b4c006bce819f1e70f8b51cc4b72bc6db48f7e203d8f0dea1a0e62ca5a19ef35ecff60574b22af49aea2d8020006d7fd608dc10be2dd0fef8e228a388bf202b1bdff909d41301ff5300d0a356d82e71ec3a6e9e439abed5b43b32cea103a6e020002d8a31b9e352117fcb6330f22e5e5f91c87f18d332bfe36ab5ec3964d4787f37b00027cd1843b657583cc97106d7b2f767198dd34b0bba0f274356aee852b03565fc721011704fac2a6ff81920b4fcecae3913a1d50a56aaa2e7f3b1996b2abfb1608e8c2

hash(P1) = 19cf606e647c4f9f88c1eaaa360be27a39dc1cf59f65703c68a188793b3a67a6

hash(P2) = 146f708d0b1afcc27733d252847deb126e66e3f6108f5143a29c210b97a9af87

hash(P3) = 01076fb850a93e9b544dce62dc47a4997120664db4cf4052132b1ba0f58e71ec

##### Public Key Matrix (PK)

Let's build our Public Key Matrix, which means, let's get the participants (members) of our two-dimensional ring as represented in figure 1.

The members of both rings can be retrieved by looking at the field ['vin']['key_offsets'] stored in a transaction. The members are previous transactions outputs (stealth addresses and commitments) that contains funds to be spent from.

It is important to notice that every output (stealth address and commitment) is stored by its index number (which is the index position where that output can be found in the pile of its respective amount). This index number is what the key_offset represents. Alongside with that, we can also retrieve the block_height and tx_id.

In the case of a RCTTypeSimple transaction, we also have one pseudo output for each input (or key_image). Which can be retrieved from a transaction at the 'pseudoOuts' field. Its meaning is better explained at the Amounts section.

Finally, from the key_offsets, let's retrieve what we need to build our Public Key Matrix which has the dimensions $n x 2$, where $n$ is the number of participants in one ring which is the length of the key_offsets vector.

For the first dimension, we need to extract the 'key' which represents the stealth address of a previous transaction.

For the second dimension, we need to extract the 'mask' which represents the commitment to a value of a previous transaction.

Therefore, our algorithm to build the Public Key Matrix, with $n$ being the length of the key_offsets, looks like:

RCTTypeFull (Type 1)

For $i = 0 .. n$:

$PK[i][0] = \text{get_outs(key_offsets[}i\text{])["key"]}$

$PK[i][1] = \text{get_outs(key_offsets[}i\text{])["mask"]}-(\sum \text{outPk}) - fee*H$

RCTTypeSimple (Type 2)

For $i = 0 .. n$:

$PK[i][0] = \text{get_outs(key_offsets[}i\text{])["key"]}$

$PK[i][1] = \text{get_outs(key_offsets[}i\text{])["mask"]}-\text{pseudoOuts}$

Notice that we are only verifying one input or key_image at a time. We need to repeat this procedure for as many inputs that a transaction has.

See an example below.

Let's look at transaction 2790982c9a44ae9516e395735c58f607d13741f8fd054bc096997aebc0a280d3

It is a RCTTypeSimple and we have two inputs, therefore, we need to build two PK matrices.

First matrix:

Stealth addresses ring members:

PseudoOuts:
85de4f299dd2539b2dbfcfee053a2cbf5a9603f912db63b1e480bea628ea8f69

PK:
PK[0][0] = 4722616327f02e9b1327f12af217107a7b8213183303c96841aaa6dc78941698
PK[0][1] = 0c952bf4efc128048303e2f3fdaf8de5679140f8d35857cfd2d199a6c27f5fd9
PK[1][0] = fc5dba9a66c68f455aff200f73e32a1fd567e87269de6fc994d89216b5ff3dea
PK[1][1] = b6216ba799ae1e92b386bcd52850427b4b702b369234b88dbe983c48fd6203e2
PK[2][0] = 5836fc6029949042b2d7960db8303143c1621098fc3ffc94cb48fb1dda5c2f43
PK[2][1] = 72a1486731b7d11a38252ceabcb107292a71fb3c7d97b8e830f7da34cb54db62
PK[3][1] = 740d5ea5b36b5764609539122067b3034a8c249bfb6e18a8c3af1b8f8f11a5b4
PK[4][0] = ba4ea9f7a1a59d4651b58782807c2166a5354c229e97373540eeb33cb4301a42
PK[4][1] = 6b19f56cb7eccfef450da65cdb98cb58e8a801cac54c66fa6099584650357b33
PK[5][1] = 0799c43b3d4874352b068f8e1b019b03a49bfb0465f47352773a4a556cfd1769


Second matrix:

Stealth addresses ring members:
[91a60d549d2dba92cf5c9f4a997e0001d22cd464392b71201c6aacd5fbfbb1df, acd55ff140c8a017407bba04b665eb8e56c116eb59d61621a618e28df42fbcb4, 9278721a78e9acead7271fe711a51307cb5f54fa60bbe89ffd314e7440a43e49, ba75ab77e640f2144c76da796b802a0f6dc1f5061315ce15f8b5b8d2c31bd348, e94ef2edefdc4026d76a84b38e2fa00d741a721450bd9323ca9f2293778f799a, 71c7abb785e5e43b7cf47deb73d5da899b9c56b97ceabbdcab77c697640c40b2]

[910db5457f88211ccc1e20c102563482edd18905535ddf05af6d8463c62b228c, 69b7aad52592bae52171770846086ec59f1aa0a5b9ec25ce62715fb7300bde4f, 09b6a063b8f014fe149de558f98d65fc71575342f900a3919660c967d04a8f37, f411724e485a3f13f9c26491bbb98c4a679283861068baf5966e205b1c1bd41b, 04819318286d2af6b97c48c6ddf6bc29d7d4aaa22894a436b0133812eb2e3f3a, 5c23d55c642a544faaa3a5917d07fc10cd95d15c95ffd06259c485d927a33a50]

PseudoOuts:
fdc80cf66a9bf9015722fb12949b4b97f418ab016546e827fc52848f86a6802b

PK:
PK[0][0] = 91a60d549d2dba92cf5c9f4a997e0001d22cd464392b71201c6aacd5fbfbb1df
PK[0][1] = d31ee380b58b70516e45c10f85b574131f5d24b0e51f7f328abf24982b95f2cd
PK[1][0] = acd55ff140c8a017407bba04b665eb8e56c116eb59d61621a618e28df42fbcb4
PK[1][1] = d15e03051bc9269100236469d859f3713f588fd05e9c103f4b9968522669a9a2
PK[2][1] = 6c21d6d56f13186b022566b2df9c20cab81459b145afcfb7eaee7d8291180489
PK[3][0] = ba75ab77e640f2144c76da796b802a0f6dc1f5061315ce15f8b5b8d2c31bd348
PK[3][1] = b4661901a1ca90c3ac4430dc8bcd770ec56f1cb2605e30e1da6603db9ac93b85
PK[4][0] = e94ef2edefdc4026d76a84b38e2fa00d741a721450bd9323ca9f2293778f799a
PK[5][0] = 71c7abb785e5e43b7cf47deb73d5da899b9c56b97ceabbdcab77c697640c40b2
PK[5][1] = 9c78d40e583ddb2abcd1e0a7b171f43369ef87be494611b50b53b12ec6d198bc


Now let's look at transaction 618ae0d58ab6432e7438bf2dce33784bb540a0f3d9ebddf1f3ad7fb303380ca3. It is a RCTTypeFull and we have only one input.

Stealth addresses ring members:

PseudoOuts:

PK:
PK[0][0] = e715dfec4f888c0081da33298b3713c087351067d83855629c64f027cd1515c3
PK[0][1] = cddf0671d90f40f502246ca70ab0cc5634ca9eb6ee6fbfd2c2fb60862c59aed5
PK[1][0] = e3b4b0c6662ecff7973ba1068783edeacef5bebc202c75b161ce83cec3189bc2
PK[1][1] = e59efdb626eff76239e301de5328c29157102152057b487a760bfcd0653ff66f
PK[2][1] = 48304371716c495d565a2291746e19a6ef40e99fab4fd50751ffa5e7784f9077
PK[3][0] = c96ae9b6863a83bc4ee18081671b473073318872402296e2fc946a8f430e6639
PK[4][0] = 37e4af39b9264b59a4d2e9efebbffc56f97b5711bd9a50bae5060b5ba3a8efe5
PK[4][1] = c7fa9c8a65dd45e66c3bdde2ba2f1a87db9ed4b77f7398977d2e5cdcee435696


Where PseudoOuts = $(\sum \text{outPk}) - fee*H$

##### Key Image (KI)

Although we have a two-dimensional ring, we only need the image key of the ring-members corresponding to the stealth-addresses. There is no key_image associated to the Commitments.

The key image is just the secret key times the hash of the public key. It can be defined by the equation: $KI = x\mathcal{H_p}(P)$. Where $KI$ is the key image, $x$ is the secret key and $\mathcal{H_p}(P)$ is the hash mapped to a point of the public key generated by $x$.

Notice that this 'secret key' in the context of Monero is actually the secret key to unlock an output (stealth address) and it is a function that depends on the transaction public key, the secret view key, the secret spend key and the output index. Therefore, one person can create multiple key_images with the same secret view and spend keys, if this person have multiple outputs addressed to him/her, of course. The equation below shows what this secret key represents but please check how 'One-time addresses' are created, if you want to further investigate it. You will find a good resource here.

$k_t^O = \mathcal{H_s}(rK_t^v,t) + k_t^s \qquad \text{---> secret key} \\$ $K_t^O = k_t^O G \qquad \text{---> public key}\\$

Let's suppose that we have the following private (secret) key: x = 09321db315661e54fe0d606faffc2437506d6594db804cddd5b5ce27970f2e09

From it, we can derive the public key by the definition of a point in an elliptic curve.

The public key (P) is defined by: P = xG

Using the Monero elliptic curve definitions (Edwards25519), we have P = cd48cd05ee40c3d42dfd9d39e812cbe7021141d1357eb4316f25ced372a9d695

In order to make it possible to perform another point multiplication, we need to treat P as a hash and map it to a point in the elliptic curve. The $\mathcal{H_p}$ is responsible for that. In our case, $\mathcal{H_p}(P)$ is the Point $\mathcal{H_p}(P)$ = c530057dc18b4a216cc15ab76e53720865058b76791ff8c9cef3303d73ae5628

Finally, we need to perform another point multiplication, which is $KI = x\mathcal{H_p}(P)$. The key image in our case is $KI$ = d9a248bf031a2157a5a63991c00848a5879e42b7388458b4716c836bb96d96c0

##### Generating a MLSAG signature

The idea behind the MLSAG signatures is also simple. We want to obfuscate the sender by proving that someone in the ring set signed the message and transferred the funds without being able to specify exactly who.

In order to generate a MLSAG signature, let's see what we need generate_MLSAG(m,PK,sk,index)

Let's consider two random values ($\alpha_0$ and $\alpha_1$) and a set of random values $ss_{i,j}$ for $i = \{ 0,1,2,...,n-1 \; | \; i \neq \pi \}$ and $j = \{0,1\}$.

Let's define

$c_{\pi+1} = \mathcal{H}(m,[\alpha_0 G], [\alpha_0 \mathcal{H_p}(PK_{\pi,0})],[\alpha_1 G])$

and for $i = \{\pi+1,\pi+2,...,n-1,0,1,2,...\pi -1 \}$

$c_{i+1} = \mathcal{H}(m,[ss_{i,0} G + c_iPK_{i,0}], [ss_{i,0} \mathcal{H_p}(PK_{i,0})+c_iI_0],[ss_{i,1} G + c_i PK_{i,1}])$

Now let's define our signature as:

$ss_{\pi,0} = \alpha_0 - c_\pi k_{\pi,0} \qquad \text{---> where k_{\pi,0} is the secret key corresponding to the key_image K_o} \\$ $ss_{\pi,1} = \alpha_1 - c_\pi k_{\pi,1} \qquad \text{---> where k_{\pi,1} is the secret key corresponding to commitment Z} \\$

The commitment $Z$ corresponds to the value commited in the transaction and it is only possible to create this transaction if you know the information from the ECDH info sent to you. Moreover, this proves that the amounts balances as the commitment zG is a commitment to 0.

As you noticed, the signature is basically composed by the random numbers that we generated except at the index where we control the funds of one of the members. But of course, for an external reader, he cannot tell where the secret index is.

$\sigma = \{c_0,ss_{0,0},ss_{0,1},ss_{1,0},ss_{1,1},...,ss_{n-1,0},ss_{n-1,1} \}$

Let's suppose that we have the following parameters as inputs:

message = '06bc62dbfc5a9b2d408a6a68a8d3d949fd0732f8a08067faef29e58b41c73c78'

sk = [Scalar('ce3c86ee5e0174ff4314975ab48be6298801a7bfdb230de767d1847f6663fa05'),Scalar('1d4db898a3ece8d3a4982ca58b6c4deac8a54a72193570906f97d93db4d90108'),]

index = 3


Let's also suppose that we execute the following code to produce the signatures $c_0$ and $ss$:

def generate_MLSAG(m,PK,sk,index):
rows = len(PK)
cols = len(PK[0])
msg0 = ''
msg0 += str(m)

alpha0 = dumber25519.random_scalar()
aG0 = alpha0 * dumber25519.G
aHP = alpha0 * dumber25519.hash_to_point(str(PK[index][0]))
msg0 += str(PK[index][0])
msg0 += str(aG0)
msg0 += str(aHP)

alpha1 = dumber25519.random_scalar()
aG1 = alpha1 * dumber25519.G
msg0 += str(PK[index][1])
msg0 += str(aG1)

I0 = sk[0]*dumber25519.hash_to_point(str(PK[index][0]))

# import ipdb;ipdb.set_trace()
c_old = dumber25519.hash_to_scalar(msg0)
i = (index + 1) % rows
if i==0:
cc = copy.copy(c_old)

ss = misc_func.scalar_matrix(rows,cols,0)

while (i!=index):
# print('i: ',i)
msg = ''
msg += str(m)

ss[i][0] = dumber25519.random_scalar()
ss[i][1] = dumber25519.random_scalar()

L1 = ss[i][0]*dumber25519.G + c_old*PK[i][0]
R = ss[i][0]*dumber25519.hash_to_point(str(PK[i][0]))+c_old*I0
msg += str(PK[i][0])
msg += str(L1)
msg += str(R)

L2 = ss[i][1]*dumber25519.G + c_old*PK[i][1]
msg += str(PK[i][1])
msg += str(L2)

c_old = dumber25519.hash_to_scalar(msg)
# print(c_old)
i = (i+1)%rows
if i==0:
cc = copy.copy(c_old)

# import ipdb;ipdb.set_trace()
ss[index][0] = alpha0 - c_old*sk[0]
ss[index][1] = alpha1 - c_old*sk[1]

return ss, cc, I0


In the end, we would obtain:

c_0:

ss:
[[6203f5aac614406c7052ec433f4423d1367ff566ea6758f7d9e6df051442d105, 4dfdaee16dec3737b9c12a309b22265b755fc19f51f57984f3e3eef63a43ca04], [2c365272a76037e8c421877007cb19c43844d204d24918058decc62611a2f80d, fe677a4d98362960afc2e47e8a8463eb0c138def4789f2a4dcd35d65d8de1d0f], [977ba37b55a29f57157bee9053999e55f8a8610c38b140681dcea8d9fed34301, 4684efb5025f1c2c7e6263405355a7485ab0cd42b289c23db8a788ea4610850f], [7e70783dc9a008e106d1de8caa6b9075e357ba1cb757c3532149fb2a6a9d3e07, 0ca6a8814cd40184a78334a62334f2dc6ea38883d2b0ee86df9c6d5a1d768609], [3ef1fbc550cfb36a7bd42921154c1e609981b70d0f61b4b69f7f57dd6dddec05, ff25e54dbca09f28598644fa018efd05187b363ac51821a56329f62a6eafc107]]

I:
a54aee2c132cc5611eeb8bb5f6f55965a5b94eafc9c05c500be3d187d1cd56a7



##### Checking a MLSAG signature

To check if the signature is valid let's first understand the math behind it. The verification function looks like check_MLSAG(m,PK, I, $c_0$, ss), where $m$ is the message being signed, $PK$ is the matrix of public keys containing the ring members of both rings, $I$ is the key image of the first ring (the one hiding the sender), $c_0$ and $ss$ are the signatures.

1. Check if $lI_j = 0 \qquad$ (make sure that the key_image is in the correct subgroup).
2. For $i = 1..n$, where i is the quantity of members in the ring, do: $L1^= ss_{i,0}G + c_{i-1}PK_{i,0} \\$ $R^ = ss_{i,0}\mathcal{H_p}(PK_{i,0}) + c_{i-1}I\\$ $L2^ = ss_{i,1}G + c_{i-1}PK_{i,1} \\$ $c_i = \mathcal{H_s}(PK_{i,0}+L1^+R^+PK_{i,1}+L2^)$
3. If $c_n = c_0$ then the signature is valid.

Let's try to check our generated signature from the example above

We have as inputs:

message:
06bc62dbfc5a9b2d408a6a68a8d3d949fd0732f8a08067faef29e58b41c73c78

PK:

I:
a54aee2c132cc5611eeb8bb5f6f55965a5b94eafc9c05c500be3d187d1cd56a7

c_0:
d91fd7517d1a9597978c4ff6413428543ee7b16de5c283daaaa1ee274250d405

ss:


If we execute the following code:

def check_MLSAG(m,PK, I, c, ss,details=0):
rows = len(PK)
cols = len(PK[0])
c_old = copy.copy(c)

str_out = '\n'
str_out += '--------------------------------------------------------'
str_out += '\n'
str_out += 'Arguments of check_ring_signature: '
str_out += 'Prefix: ' + str(m)
str_out += '\n'
str_out += 'Key_image: ' + str(I)
str_out += '\n'
str_out += 'Public keys: ' + str(PK)
str_out += '\n'
str_out += 'Signature ss: ' + str(ss)
str_out += '\n'
str_out += 'Signature c: ' + str(c)
str_out += '\n'

i = 0
msg = ''
msg += str(m)
# import ipdb;ipdb.set_trace()
while i < rows:
toHash = ''
toHash += str(m)

str_out += 'Calculating L1 = ss[i][0] * G + ci * P[i][0]   for index = ' + str(i)
str_out += '\n'
str_out += 'Calculating R = ss[i][0] * H(P[i][0]) + ci * I   for index = ' + str(i)
str_out += '\n'

L1 = ss[i][0]*dumber25519.G + c_old*PK[i][0]
R = ss[i][0]*dumber25519.hash_to_point(str(PK[i][0]))+c_old*I

str_out += 'L1 calculated for index = ' + str(i)
str_out += '\n'
str_out += str(L1)
str_out += '\n'
str_out += 'R calculated for index = ' + str(i)
str_out += '\n'
str_out += str(R)
str_out += '\n'

toHash += str(PK[i][0])
toHash += str(L1)
toHash += str(R)

L2 = ss[i][1]*dumber25519.G + c_old*PK[i][1]
toHash += str(PK[i][1])
toHash += str(L2)

str_out += 'Calculating L2 = ss[i][1] * G + ci * P[i][1]   for index = ' + str(i)
str_out += '\n'
str_out += 'L2 calculated for index = ' + str(i)
str_out += '\n'
str_out += str(L2)
str_out += '\n'

c_old = dumber25519.hash_to_scalar(toHash)
i = i + 1
str_out += 'Calculating c_old: ' + str(c_old)
str_out += '\n'

str_out += 'Calculating c_old - c :'
str_out += '\n'
res = (c_old-c) == Scalar(0)
str_out += str(c_old-c)
str_out += '\n'
if res:
str_out += 'Transaction is valid. The signature matches the data.'
else:
str_out += 'Transaction is invalid. The signature does not match the data.'

str_out += '\n'
str_out += '--------------------------------------------------------'
str_out += '\n'
return res, str_out



We would get:


--------------------------------------------------------
Arguments of check_ring_signature: Prefix: 06bc62dbfc5a9b2d408a6a68a8d3d949fd0732f8a08067faef29e58b41c73c78
Key_image: a54aee2c132cc5611eeb8bb5f6f55965a5b94eafc9c05c500be3d187d1cd56a7
Signature ss: [[9242ca08be9b89bb5a326059bb27815ba4e096f1a8f2a52f86ab7929d9e45c0a, 4bd4e908762366ffd1a03dc7079d7b3a1ffcdc9078e6e3b3cf7f180a75baca06], [6d8c9062d1516481009489272cc544afe71624219d26785c9457f998caf52c07, aed9038c60a4eb1b3a606462107f0c2e049218ed856f72c59acba4bdd851f304], [c7fc29859250f07aea8a0c8fb4617e773b7336225e0363f0ca61fad9658c1e04, 83b5a71a70a13a05f78ca6229d6da13f3ea9f9bf5109b7052ed26f28615e5f0f], [e08e7a161847f964fed3e7261da24eed4826bd7a66bb92995318fc119b7dde08, 25f75cf7ec8d9b1d19bb08eda32525a358211ac375d3268dc801cd493d44dc0f], [59ec219e9a7af904b0ce2fde6d77b074ad3d427057ee30ae5459610b3890af0b, 2c7efebab20d7a1fafeb534974c9b8ef40acd18bca3f07eab0a1e6fb0d432505]]
Signature c: 1f5d8125aa39484dc7ba932f541f3435b88cd7d3330a62486dc2b0f5785c0d0e
Calculating L1 = ss[i][0] * G + ci * P[i][0]   for index = 0
Calculating R = ss[i][0] * H(P[i][0]) + ci * I   for index = 0
L1 calculated for index = 0
0e30f8c8ae8ce1d5679391bea6b93ae9d5f5af951c30d5ba2a96371310ca153c
R calculated for index = 0
Calculating L2 = ss[i][1] * G + ci * P[i][1]   for index = 0
L2 calculated for index = 0
e1e63f51d1581366e48a14b36bc9249a49a4cbc8f2be2bd02ea5ed8e582d7212
Calculating L1 = ss[i][0] * G + ci * P[i][0]   for index = 1
Calculating R = ss[i][0] * H(P[i][0]) + ci * I   for index = 1
L1 calculated for index = 1
9e96536a163df0b1a571616f2d1bfe4da2e2477c320477fc46f3155c41865031
R calculated for index = 1
2d0f7013e6bb584a0f8c894b32d64504de90b4e766f5b659969e6514d64a2379
Calculating L2 = ss[i][1] * G + ci * P[i][1]   for index = 1
L2 calculated for index = 1
1683d1df51a3343d6a631c366dbe79c03471799264b016459088c99d6276df65
Calculating c_old: 40bc611e9f93cbe4cc495cffa7ec1d1261ef45e5650cfb2c1072518232023408
Calculating L1 = ss[i][0] * G + ci * P[i][0]   for index = 2
Calculating R = ss[i][0] * H(P[i][0]) + ci * I   for index = 2
L1 calculated for index = 2
R calculated for index = 2
7f1b8d59f34849b4a42a1f729556b4e5af8538cc3b65bfd317e78a9c93a78031
Calculating L2 = ss[i][1] * G + ci * P[i][1]   for index = 2
L2 calculated for index = 2
2338878931c204ac396cf2749546aae9107082f00ed52f721ae42a6576b5562d
Calculating c_old: cb486b66f020e68a45b95a1cbab37a42bf3df9de502ac6398db53af89021ca02
Calculating L1 = ss[i][0] * G + ci * P[i][0]   for index = 3
Calculating R = ss[i][0] * H(P[i][0]) + ci * I   for index = 3
L1 calculated for index = 3
db1dafa40e7bace38059431fb82e7cf94562cc28b186709fdfa7978274011fa7
R calculated for index = 3
87782e0fe91131529d016b167de160b389ac0ea65a6e7beeedd33145fdf60b29
Calculating L2 = ss[i][1] * G + ci * P[i][1]   for index = 3
L2 calculated for index = 3
Calculating c_old: 6ac15205e388ab0a22c36bec5a472f05861a489afc901f083639a61ed9a14301
Calculating L1 = ss[i][0] * G + ci * P[i][0]   for index = 4
Calculating R = ss[i][0] * H(P[i][0]) + ci * I   for index = 4
L1 calculated for index = 4
ba7663eed964fc76953a72b381b053d718e796c8daaf993f7fbcaeeb30b99d50
R calculated for index = 4
406c97b76741617d57b1c986d6374593e6d1f3898310c8b463e9af9b3015b412
Calculating L2 = ss[i][1] * G + ci * P[i][1]   for index = 4
L2 calculated for index = 4
c5a23a6c711800a9f50067868f41334f15280344f3816aeaeab16eb8a3e56ab3
Calculating c_old: 1f5d8125aa39484dc7ba932f541f3435b88cd7d3330a62486dc2b0f5785c0d0e
Calculating c_old - c :
0000000000000000000000000000000000000000000000000000000000000000
Transaction is valid. The signature matches the data.
--------------------------------------------------------