KomodoPlatform / pycc

Crypto-Conditions for Komodo in Python
5 stars 5 forks source link

pycctx: bad scriptPubKey encoding for some condition structures #15

Open Alrighttt opened 4 years ago

Alrighttt commented 4 years ago

https://github.com/Alrighttt/pycc-1/commit/88e7c4df7692904d7727dcfb03eef0c3a53c09fa

There is a test here in my fork which (I believe) demonstrates that pycctx is producing bad signatures for conditions with the following structure: {'type': 'threshold-sha-256', 'threshold': 3, 'subconditions': [{'type': 'threshold-sha-256', 'threshold': 1, 'subconditions': [{'type': 'secp256k1-sha-256', 'pubkey': '03682b255c40d0cde8faee381a1a50bbb89980ff24539cb8518e294d3a63cefe12', 'signature': 'b768f3ea235aae45ccad0d7b6907a6c45b22ab9b16eeee7db38bb2a181d576317aa18fa8664eaf5d16c7a676b189bbd6ecce9291fcd48e77940574beb6b8ee20'}]}, {'type': 'eval-sha-256', 'code': 'e4'}, {'type': 'eval-sha-256', 'code': 'e5'}]}

It however does not have any issue with the "typical" structure: {'type': 'threshold-sha-256', 'threshold': 2, 'subconditions': [{'type': 'threshold-sha-256', 'threshold': 1, 'subconditions': [{'type': 'secp256k1-sha-256', 'pubkey': '03682b255c40d0cde8faee381a1a50bbb89980ff24539cb8518e294d3a63cefe12', 'signature': '3956faf39b6c3d051e03f883dc9067ede9fce0d29f13d3a9b8b59775ca874e8357a66829ad7c41712303c48e3a5dda40d5e25e641f45436e51a7ff2616fdc6de'}]}, {'type': 'eval-sha-256', 'code': 'e4'}]}

Alrighttt commented 4 years ago

While trying to broadcast one of these (seemingly) bad signature transactions. Komodod's validation catches it here: https://github.com/Alrighttt/komodo/blob/bd7cc1f36f76afea54b12fa6db276f45e0c89663/src/cryptoconditions/src/cryptoconditions.c#L291

Alrighttt commented 3 years ago

I have done further research regarding this issue. I believe the underlying cause for these bad signatures is actually pycctx's inability to properly encode some condition structures.

I have made modifications to the createrawtransaction rpc command in my cc_fuzz branch. . This code is only intended to be used for ease of testing. It can be used to generate a scriptpubkey for a given condition. It can then be easily compared against the scriptpubkey created by pycctx with the same condition.

First I will show a working example, and demonstrate how I am testing this.

komodo-cli -ac_name=PYCC decoderawtransaction $(komodo-cli -ac_name=PYCC createrawtransaction '[{"txid":"0000000000000000000000000000000000000000000000000000000000000000","vout":0}]' '{"condition":{"type":"threshold-sha-256","threshold":2,"subfulfillments":[{"type":"eval-sha-256","code":"5A"},{"type":"threshold-sha-256","threshold":1,"subfulfillments":[{"type":"secp256k1-sha-256","publicKey":"03682B255C40D0CDE8FAEE381A1A50BBB89980FF24539CB8518E294D3A63CEFE12"}]}]}}')

This will generate the following scriptpubkey:

  "scriptPubKey": {
    "asm": "a22c8020e029c511da55523565835887e412e5a0c9b920801b007000df45e545f25028248103120c008203000401 OP_CHECKCRYPTOCONDITION",
    "hex": "2ea22c8020e029c511da55523565835887e412e5a0c9b920801b007000df45e545f25028248103120c008203000401cc",
    "reqSigs": 1,
    "type": "cryptocondition",
    "addresses": [
      "R9zHrofhRbub7ER77B7NrVch3A63R39GuC"
    ]
  }

This is the "typical" condition structure. This structure works as intended. If we now generate it with pycctx:

>>> from pycctx import *
>>> cond = cc_threshold(2, [cc_eval(bytes([228])), cc_threshold(1, [cc_secp256k1("03682B255C40D0CDE8FAEE381A1A50BBB89980FF24539CB8518E294D3A63CEFE12")])])
>>> cond.to_anon().to_py()
{'type': 'threshold-sha-256', 'condition': 'a22c8020e029c511da55523565835887e412e5a0c9b920801b007000df45e545f25028248103120c008203000401'}

Take note that the scriptPubkey matches. Another thing to note is that the eval code given to createrawtransaction is base64, so "code":"5A" does correlate with bytes([228]).

Now if we were to add an additional eval to this condition structure:

komodo-cli -ac_name=PYCC decoderawtransaction $(komodo-cli -ac_name=PYCC createrawtransaction '[{"txid":"0000000000000000000000000000000000000000000000000000000000000000","vout":0}]' '{"condition":{"type":"threshold-sha-256","threshold":3,"subfulfillments":[{"type":"eval-sha-256","code":"5A"},{"type":"eval-sha-256","code":"5Q"},{"type":"threshold-sha-256","threshold":1,"subfulfillments":[{"type":"secp256k1-sha-256","publicKey":"03682B255C40D0CDE8FAEE381A1A50BBB89980FF24539CB8518E294D3A63CEFE12"}]}]}}')
      "scriptPubKey": {
        "asm": "a22c80208666071e49c77ae44964ed3a67fab598e9de358305195412b7de686afdd817f781032210008203000401 OP_CHECKCRYPTOCONDITION",
        "hex": "2ea22c80208666071e49c77ae44964ed3a67fab598e9de358305195412b7de686afdd817f781032210008203000401cc",
        "reqSigs": 1,
        "type": "cryptocondition",
        "addresses": [
          "RRpPnBXrWBopE7iNACWttxb6N8R2rZ4taj"
        ]
      }
>>> from pycctx import *
>>> cond = cc_threshold(3, [cc_eval(bytes([228])), cc_eval(bytes([229])), cc_threshold(1, [cc_secp256k1("03682B255C40D0CDE8FAEE381A1A50BBB89980FF24539CB8518E294D3A63CEFE12")])])
>>> cond.to_anon().to_py()
{'type': 'threshold-sha-256', 'condition': 'a22c8020a17c619189155680b507999b20e3e8fc1dc4a60481299b85b57b96849f34b51c81032210008203000401'}

The scriptPubkey does not match.

I believe this is ultimately causing these bad signatures. It is not an issue with the signing function, but the sighash function receiving the wrong previous scriptPubKeys while creating signatures for CC inputs.

I have done further test cases, and I can see no clear pattern for when it works or when it doesn't. These are a bit rough ( gists were just thrown together to share with mihailo and dimxy ), but you should be able to understand the testing method. tests: https://gist.github.com/Alrighttt/8655e3a040bd49dc3dd72befd33bf0fe results: https://gist.github.com/Alrighttt/a27ddf3fb5b2df42f3d53ac054dbfb95

Please comment here if there is any confusion or you need any further information.

Alrighttt commented 3 years ago

dimxy has shared some further research in one of our chat channels. Adding it here just to keep track of it.

from dimxy: I traced the threshold fingerprint function It looks like rust and c++ lib cryptoconditions sort identical subconditions differently: trace for a cond with two eval codes for c++:

subconds begin
asn=A22B8020AC349E1A9CC6A98C52CF7015EFFD7B6FAF41DF8003E08EA79FF4D334BB53A9B5810302040082020204
asn=AF2780205E1EFFE9B7BAB73DCE628CCD9F0CBBB16C1E6EFC6C4F311E59992A467BC119FD8103100000
asn=AF278020AB61BA11A38B007FF98BAA3AB20E2A584E15269FD428DB3C857E2A2D568B57258103100000
subconds end
for rust:
subconds begin
asns[]=a22b8020ac349e1a9cc6a98c52cf7015effd7b6faf41df8003e08ea79ff4d334bb53a9b5810302040082020204
asns[]=af278020ab61ba11a38b007ff98baa3ab20e2a584e15269fd428db3c857e2a2d568b57258103100000
asns[]=af2780205e1effe9b7bab73dce628ccd9f0cbbb16c1e6efc6c4f311e59992a467bc119fd8103100000 <--- inverted with previous
subconds end