I'm trying to decode outputs of transactions using the general algorithm described here. It flawlessly identifies my outputs, which proves I properly extract keys from extra and calculate valid Hs(8aR||i), but then I have strange errors with amount decryption.

Most puzzling is that it actually works in some cases, while for most transactions it generates garbled data. Given the simplicity of operation (hash and xor) either I make some stupid mistake or I get invalid amount data from the transaction.

I looked into Monero source code and it seems I don't miss anything in the algorithm.

Here's my Python code snippet enriched with tons of debug prints. Please note I skipped the part of output identification, because it works.

print("    Hs         = {:s}".format(binascii.hexlify(Hs).decode()))    # Hs is Hs(8aR||i)
amount_hs = sha3.keccak_256(b"amount" + Hs).digest()
print("    amount_hs  = {:s}".format(binascii.hexlify(amount_hs).decode()))
    "    enc_amount = {:s}".format(
)    # encamount is tx["rct_signatures"]["ecdhInfo"][oidx]["amount"]


xormask = amount_hs[: len(output.encamount)]
print("    xormask    = {:s}".format(binascii.hexlify(xormask).decode()))
dec_amount = bytes(a ^ b for a, b in zip(output.encamount, xormask))
print("    dec_amount = {:s}".format(binascii.hexlify(dec_amount).decode()))
int_amount = int.from_bytes(
    dec_amount, byteorder="little", signed=False
print("    int_amount = {:d}".format(int_amount))
amount = monero.numbers.from_atomic(int_amount)
print("    amount     = {:.12f}".format(amount))

An example of transaction that passes properly: f4a11b4b3fb38f737d9cdcc088cd11b00379c4106b4e85132c2a8eeea41febfa:

Wallet master address: 56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj
 Receiving subaddress: 7ACPcxXBfZaPTsfzMXGKYyfhjcQXQYsTYhEfRx4qTb1fJDgK6EHHVHr47PSYpgMAzY1Vza6C7EcF5JQWV1PVckxTBd1CeVc
      secret view key: e507923516f52389eae889b6edc182ada82bb9354fb405abedbe0772a15aea0a
checking tx: f4a11b4b3fb38f737d9cdcc088cd11b00379c4106b4e85132c2a8eeea41febfa
tx has   2 outputs
tx has   1 keys
checking output 0
  checking key 0: 1bd7ed2b23b246e91450e0cf28bb0fed471dec68d222b0f65c79928df73d83df
     Hs         = 52183965dafe3f7a77e5d544420a6fe6a1dcc9ab4a3d2a6a2559cf3639d31e09
     amount_hs  = e3b6b0fbaa7a5d285133dbb985976230a89c07e8392f2cb2b5bf0ccecf814cfb
     enc_amount = 2bb24b8ab57b5d28
     xormask    = e3b6b0fbaa7a5d28
     dec_amount = c804fb711f010000
     int_amount = 1234567890120
     amount     = 1.234567890120
checking output 1
  checking key 0: 1bd7ed2b23b246e91450e0cf28bb0fed471dec68d222b0f65c79928df73d83df
    no match

However, another one fails miserably: 5e149f7f97f55afb99ecdf7836bea6ef9a0e61202ffcd466300da56b2b083c00

Wallet master address: 56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj
 Receiving subaddress: 79X56BnG8Ji4G7yCeVvrhvJHbbTyf8Y7XHGo7TUvwJRbjhyDcQM36Na7mkyS33S6WpaFt1WKHuq9AQXotpFbhSC3RZEnNV3
      secret view key: e507923516f52389eae889b6edc182ada82bb9354fb405abedbe0772a15aea0a
checking tx: 5e149f7f97f55afb99ecdf7836bea6ef9a0e61202ffcd466300da56b2b083c00
tx has   2 outputs
tx has   1 keys
checking output 0
  checking key 0: 7a4521bc639bc4398bbbd07c89096348d70189a3f69593b248b2a7cd374dab1f
    no match
checking output 1
  checking key 0: 7a4521bc639bc4398bbbd07c89096348d70189a3f69593b248b2a7cd374dab1f
     Hs         = 4e2918eb07facce065c1fcfe610a5b7d18064d64a7b8844d1844872f6d60159f
     amount_hs  = 2c3fbf97e7afe8a91c1ea2366a781335cbfe2ae955f2c35e79c758f57bba4679
     enc_amount = f10a49feef2673e3
     xormask    = 2c3fbf97e7afe8a9
     dec_amount = dd35f66908899b4a
     int_amount = 5376041249427305949
     amount     = 5376041.249427305949

The real amount here is 3.141592653523 but for some reason 5376041.249427305949 appears.

  • You say it flawlessly identifies your outputs but your second example doesn't match output at index 0 but then does at index 1. If the tx is destined for you, every output key you should own? – jtgrassie Feb 3 '20 at 16:44
  • 1
    Why should it match output 0? It's change and it belongs to the sender, not me. – emesik Feb 3 '20 at 22:56
  • Ah, yes right, change. I wrongly assumed you were checking a tx where both outputs were owned by / sent to you. Props to @knaccc for spotting the missing reduce. – jtgrassie Feb 4 '20 at 2:02

The Hs value in your failing transaction is incorrect. You've written 4e2918eb07facce065c1fcfe610a5b7d18064d64a7b8844d1844872f6d60159f but it should be f9b573a61a7e27c8dc3d48448e4184c117064d64a7b8844d1844872f6d60150f.

This is happening because your Hs function is doing keccak but not sc_reduce32. You were lucky that in the first transaction, the result of the keccak was lower than the "l" value used in sc_reduce32, so it didn't matter in the first transaction that you didn't do it.

  • Thank you! I still haven't understood how in the world the invalid value of Hs allowed me to properly identify outputs. That ensured me the calculation was solid and I was looking for bug somewhere else. – emesik Feb 3 '20 at 23:54
  • 2
    @emesik It works because a mod l of that Hs(8aR||i) value automatically happens during scalar multiplication with G, because l is the same as the group size of G. You're lucky that your ed25519 library treats the out-of-bounds Hs number properly during that scalar multiplication. – knaccc Feb 4 '20 at 1:34
  • Right! The recognition dramatically improved, but still fails sometimes. Is it possible that sc_reduce32 does more than just modulo l? – emesik Feb 4 '20 at 11:17
  • 1
    @emesik All sc_reduce32 does is mod l. If you have another example of a failing tranasction, please post it. It's possible you may be looking at an older transaction where amounts are 32 byte scalars and need to be decoded differently than 8 byte amounts. – knaccc Feb 4 '20 at 23:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.