5

I was looking for scripts on the blockchain to test my program, and I encountered a weird scriptPubKey in this tx that I couldn't understand.

The opcodes are

pk1 = 02085C6600657566ACC2D6382A47BC3F324008D2AA10940DD7705A48AA2A5A5E33
pk2 = 03F5D0FB955F95DD6BE6115CE85661DB412EC6A08ABCBFCE7DA0BA8297C6CC0EC4
hash1 = D68DF9E32A147CFFA36193C6F7C43A1C8C69CDA530E1C6DB354BFABDCFEFAF3C
hash2 = F531F3041D3136701EA09067C53E7159C8F9B2746A56C3D82966C54BBC553226

OP_PUSHDATA <pk1>
OP_CHECKSIG
OP_SWAP

OP_PUSHDATA <pk2>
OP_CHECKSIG
OP_SWAP

3 OP_PICK
OP_SHA256
OP_PUSHDATA <hash1>
OP_EQUAL

3 OP_PICK
OP_SHA256
OP_PUSHDATA <hash2>
OP_EQUAL

OP_BOOLAND

4 OP_PICK
OP_SIZE OP_NIP
OP_PUSHDATA 0x20
OP_PUSHDATA 0x22
OP_WITHIN

OP_BOOLAND

3 OP_PICK OP_NIP
OP_PUSHDATA 0x20
OP_PUSHDATA 0x22
OP_WITHIN

OP_BOOLAND

OP_IF

3 OP_PICK
OP_SIZE OP_NIP

3 OP_PICK
OP_SIZE OP_NIP

OP_EQUAL
OP_PICK

OP_ELSE
OP_BOOLAND
OP_ENDIF

I tried to somehow decipher it, and this is the pseudocode in my understanding

bool pk_script(dat1, dat2, sig1, sig2) {
    if (sha256(dat1) == hash1 &&
        sha256(dat2) == hash2 &&
        size(dat1) == 32 or 33 &&
        size(dat2) == 32 or 33) {
        if (size(dat1) == size(dat2)) {
            return checksig(sig2, pk2);
        } else {
            return checksig(sig1, pk1);
        }
    } else {
        return checksig(sig1, pk1) && checksig(sig2, pk2);
    }
}

If this is correct, I can't understand what the condition of the main if is trying to do, nor can I see the meaning of the first branch.

The output was spent in this tx which only pushes two empty strings and two signatures in the scriptSignature.

Could anyone please explain what's the meaning of the rest of the script besides checking the validity of two signatures?

7
  • what a wonderful script! I am trying to decode, but have some troubles. I am having troubles to verify the spending tx signatures with the first or second pubkey, no matter if I use the normal hash or a double hash. One observation: sha256 of the 20 bytes hash from the funding tx (0x1B1B01DC...) results in hash2 in the TX_OUTPUT of the spending tx (F531F304...). So this all makes me think that it is a multisig try. Working on it ... Commented Feb 23, 2018 at 17:28
  • sorry forgot to update after Andrew's reply. That's an interesting discovery. I don't know why the owner would expose part of the answer in the previous tx if he was intended to make it a practical and secure one(don't know much about lightning network though). And my raw tx for signature is this one, which seemed fine when I checked.
    – Rod Lin
    Commented Feb 24, 2018 at 10:00
  • when I convert the sig and unsigned tx to hex, and do a double sha256 on it, I get "4eb4dccd727e81315a9ff801c205efc62635471cf8668e42c1c8aebfb51500a3" - which doesn't match somehow (I then use: openssl pkeyutl <tmp_utx_dsha256.hex -verify -pubin -inkey pubkey.pem -sigfile tmp_sig.hex). How did you check, that sig is valid? Commented Feb 24, 2018 at 12:53
  • I was using a python library and did get the same hash. I tried openssl in your way, and it still seemed to be valid. These were the parameters I used.
    – Rod Lin
    Commented Feb 24, 2018 at 15:25
  • ah, I see... this is your own tx. That works with me too :-) I was trying the original tx, but couldn't get it to work. The sig/pubkey and hash didn't match. Anyhow, the structure of the original tx is clear, and I don't want to get off topic too much. Thx. Commented Feb 24, 2018 at 17:03

2 Answers 2

3

Your pseudocode is correct.

The script has two parties involved in it. If either party has two hash preimages, they can sign for the transaction and spend the coins. Or both parties can spend the coins if both of them sign for the transaction.

So presumably each party generates a secret value which is their hash preimage. If some condition is violated, then one party might be forced to give up their hash preimage to the other party thus allowing the other party to spend all of the coins associated with the output.

But there could also be a cooperative spend where both of them sign for the transaction. This is actually similar to what the Lightning Network does for commitment transactions.

Instead of using this script, they could have done a 2-of-2 multisig and revealed the other person's private key just for the case with the revealed hash preimage. However this may not be ideal because revealing private keys could compromise other private keys.

3
  • Wait but there is an OP_PICK after OP_EQUAL. So shouldn't it pick the index 1 item(checksig(sig2, pk2)) in the stack if (size(dat1) == size(dat2)) is true or return index 0 item(checksig(sig1, pk1)) if it is false?
    – Rod Lin
    Commented Feb 24, 2018 at 1:09
  • Hmm, indeed, I missed that. I'll edit my answer.
    – Ava Chow
    Commented Feb 24, 2018 at 1:22
  • Thanks Andrew! I'm still a little unclear about the condition (size(dat1) == size(dat2)). Why does it have to compare the size of the two preimages to determine which public key to use?
    – Rod Lin
    Commented Feb 24, 2018 at 2:35
4

Trying to see, how this tx was successfully confirmed (in 2013) in the blockchain should reveal the secret of this script. The previous comments give already an idea, what the script is doing, but doesn't explain all. So I try to decompose further. Assumption is, that sigscript of the spending tx goes to stack, then followed on top by the pubkey script. To verify the signature, the previous funding tx would need to be converted to an unsigned tx, so I remove the sig from orig tx, replace it with pubkey script, and change input script length:

 01000000 01 A214A2DAF91691AFDD491FD00D894EB3301E35BC18B5554B14E12843037E954C (<-- reverse hex !)
  00000000
  23         (<-- length is decimal 35 Bytes, reversed, hex 0x23)
  2102085C6600657566ACC2D6382A47BC3F324008D2AA10940DD7705A48AA2A5A5E33AC7C2103F5D0FB955F95DD6BE6115CE85661DB412EC6A08ABCBFCE7DA0BA8297C6CC0EC4AC7C5379A820D68DF9E32A147CFFA36193C6F7C43A1C8C69CDA530E1C6DB354BFABDCFEFAF3C875379A820F531F3041D3136701EA09067C53E7159C8F9B2746A56C3D82966C54BBC553226879A5479827701200122A59A5379827701200122A59A6353798277537982778779679A68 (<-- placed pkscript here into sigscript section)
  00000000   (<-- interestingly, Sequence is not FFFFFFFF)
 01 C06C3C0000000000 23 21039DC85F5FE062D4EEF0470FA96D4BBCFFF0096C62042333CD05AD491536560443AC DA538652

which is serialized and double sha256 hashed:

01000000014c957e034328e1144b55b518bc351e30b34e890dd01f49ddaf9116f9daa214a200000000B52102085C6600657566ACC2D6382A47BC3F324008D2AA10940DD7705A48AA2A5A5E33AC7C2103F5D0FB955F95DD6BE6115CE85661DB412EC6A08ABCBFCE7DA0BA8297C6CC0EC4AC7C5379A820D68DF9E32A147CFFA36193C6F7C43A1C8C69CDA530E1C6DB354BFABDCFEFAF3C875379A820F531F3041D3136701EA09067C53E7159C8F9B2746A56C3D82966C54BBC553226879A5479827701200122A59A5379827701200122A59A6353798277537982778779679A680000000001c06c3c00000000002321039dc85f5fe062d4eef0470fa96d4bbcfff0096c62042333cd05ad491536560443acda53865201000000

4eb4dccd727e81315a9ff801c205efc62635471cf8668e42c1c8aebfb51500a3

This double sha256 hash, pubkeys and sig (from the spending tx) matches with these pubkeys:

ssig1:
30450221009a29101094b283ae62a6fed68603c554ca3a624b9a78d83e8065edcf97ae231b02202cbed6e796ee6f4caf30edef8f5597a08a6be265d6601ad92283990b55c038fa pubkey in HEX: 03F5D0FB955F95DD6BE6115CE85661DB412EC6A08ABCBFCE7DA0BA8297C6CC0EC4 pubkey in PEM: MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgAD9dD7lV+V3WvmEVzoVmHbQS7GoIq8v859oLqCl8bMDsQ= double sha256: 4eb4dccd727e81315a9ff801c205efc62635471cf8668e42c1c8aebfb51500a3

ssig2:
3044022045d08719828fbd93e49c9223e63f4d2dab2de6c568e1faa2cccb33adf2575d2c02200c00126cb0105275040a963d91e45460147e40451b590485cf438606d3c784cf pubkey in HEX: 02085C6600657566ACC2D6382A47BC3F324008D2AA10940DD7705A48AA2A5A5E33 pubkey in PEM: MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgACCFxmAGV1ZqzC1jgqR7w/MkAI0qoQlA3XcFpIqipaXjM= double sha256: 4eb4dccd727e81315a9ff801c205efc62635471cf8668e42c1c8aebfb51500a3

Quick verification with openssl (hence the pubkey in PEM format):

sig=30450221009a29101094b283ae62a6fed68603c554ca3a624b9a78d83e8065edcf97ae231b02202cbed6e796ee6f4caf30edef8f5597a08a6be265d6601ad92283990b55c038fa
pk=03F5D0FB955F95DD6BE6115CE85661DB412EC6A08ABCBFCE7DA0BA8297C6CC0EC4
hash=4eb4dccd727e81315a9ff801c205efc62635471cf8668e42c1c8aebfb51500a3
printf $( echo $hash | sed 's/[[:xdigit:]]\{2\}/\\x&/g' ) > tmp_utx_dsha256.hex
echo "MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgAD9dD7lV+V3WvmEVzoVmHbQS7GoIq8v859oLqCl8bMDsQ=" > cat pubkey.pem
printf $( echo $sig | sed 's/[[:xdigit:]]\{2\}/\\x&/g' ) > tmp_sig.hex
openssl pkeyutl <tmp_utx_dsha256.hex -verify -pubin -inkey pubkey.pem -sigfile tmp_sig.hex

OK, the part for the signature is clear. So looking further at the pkscript, I'll try to decode:

pubkey1  21 02085C6600657566ACC2D6382A47BC3F324008D2AA10940DD7705A48AA2A5A5E33 AC 7C
pubkey2  21 03F5D0FB955F95DD6BE6115CE85661DB412EC6A08ABCBFCE7DA0BA8297C6CC0EC4 AC 7C
         53 79 A8 
hash1    20 D68DF9E32A147CFFA36193C6F7C43A1C8C69CDA530E1C6DB354BFABDCFEFAF3C
         87 53 79 A8 
hash2    20 F531F3041D3136701EA09067C53E7159C8F9B2746A56C3D82966C54BBC553226
         87 9A 54 79 82 77 
         01 20
         01 22
         A5 9A 53 79 82 77 
         01 20
         01 22
         A5 9A 63 53 79 82 77 53 79 82 77 87 79 67 9A 68

The problem I see is, that your statement somehow doesn't seem to match:

if (sha256(dat1) == hash1 &&
    sha256(dat2) == hash2 &&
    size(dat1) == 32 or 33 &&
    size(dat2) == 32 or 33) {
    if (size(dat1) == size(dat2)) {
       return checksig(sig2, pk2);
    } else {
       return checksig(sig1, pk1);
    }

The stack would look after sigscript:

[ssig2]
[ssig1]
[0x00]
[0x00]

And on top the pkscript would follow:

21: OP_DATA_0x21:        length compressed Public Key (X9.63 form, 33 Bytes)
    02085C6600657566:ACC2D6382A47BC3F:324008D2AA10940D:D7705A48AA2A5A5E:33
    corresponding bitcoin address is:   152q849uVmoB5oRcZ4d4tJHyRuB6FPB9Hz  
AC: OP_CHECKSIG:         sig must be a valid sig for hash and pubkey

-- this would check the pubkey1 against ssig2, remove pubkey and [ssig2] from stack, and leave [TRUE] on stack

7C: OP_SWAP:             top two items on stack are swapped

-- this would swap [ssig1][TRUE], so [ssig1] is on top of stack

21: OP_DATA_0x21:        length compressed Public Key (X9.63 form, 33 Bytes)
    03F5D0FB955F95DD:6BE6115CE85661DB:412EC6A08ABCBFCE:7DA0BA8297C6CC0E:C4
    corresponding bitcoin address is:   1BaJ2fYiAVnZC73MtX1LsyJBtXNZWDSkt6
AC: OP_CHECKSIG:         sig must be a valid sig for hash and pubkey

-- this would check the pubkey2 against ssig1, remove pubkey and [ssig1] from stack, and leave [TRUE] on stack [0x00][0x00][TRUE][TRUE]

7C: OP_SWAP:             top two items on stack are swapped

-- this would swap [TRUE][TRUE], reason is unclear

53: OP_3:                the number 3 is pushed onto stack

-- top stack element is "3" [0x00][0x00][TRUE][TRUE][0x53]

79: OP_PICK:             item n back in stack is copied to top

-- this would pop 1 element from stack, and bring [0x00] to the top of stack: [0x00][0x00][TRUE][TRUE][0x00]

A8: OP_SHA256:           input is hashed using SHA-256
20: OP_Data:             next 32 bytes is data to be pushed on stack
    D68DF9E32A147CFF:A36193C6F7C43A1C:8C69CDA530E1C6DB:354BFABDCFEFAF3C
87: OP_Equal:            Returns 1 if inputs are equal, 0 otherwise

-- hashed the [0x00] value, put OP_DATA on top, and compare it. OP_EQUAL pops two elements from stack. This would give [FALSE] on the stack. Stack status: [0x00][0x00][TRUE][TRUE][FALSE]

53: OP_3:                the number 3 is pushed onto stack
79: OP_PICK:             item n back in stack is copied to top

-- this would pick the [TRUE] value ??

A8: OP_SHA256:           input is hashed using SHA-256
20: OP_Data:             next 32 bytes is data to be pushed on stack
    F531F3041D313670:1EA09067C53E7159:C8F9B2746A56C3D8:2966C54BBC553226
87: OP_Equal:            Returns 1 if inputs are equal, 0 otherwise

-- hashed the [0x00] value, put OP_DATA on top, and compare it. OP_EQUAL pops two elements from stack. This would give [FALSE] on the stack. Stack status: [0x00][0x00][TRUE][TRUE][FALSE][FALSE]

-- interestingly, this hash would match to sha256(hash) provided in the funding tx, as mentioned in the previous comment. I stop further evaluation of the script here. My understanding is, that we have seen the spending script with two hex 0x00 values, followed by sigs. This does not match the pseudo code? Maybe more like this:

if (sig1 == TRUE && 
    sig2 == TRUE &&
    sha256(dat1) == hash1 &&
    sha256(dat2) == hash2 &&
    size(dat1) == 32 or 33 &&
    size(dat2) == 32 or 33) 
    ...

However: I have somewhere an "off by one" at the second hash value (F531F3041D313670...). It should be OP_4 or OP_5 (instead of OP_3) to reach to one of the provided hash images. With the value 3 the OP_PICK would copy [TRUE] (from the sig verification) on top, instead the hash image at position 4 or 5...

I didn't follow the next opcodes, there are more conditions. Also sequence number is "00000000" (not "FFFFFFFF"), and LockTime is "DA538652" (decimal ), which is bigger than 500'000'000, meaning a unix timestamp. This translates to a date: Fr 15 Nov 2013 18:03:22 CET. The tx appeared in the blockchain on 2013-11-15 17:13:23.

So in summary I think, this tx was structured in a way, that someone gave out a keyword with the tx to a second party, and you needed to be able to sign and "know" the keyword. A type of multisig, yes. And, it could not be spent before a certain point in time (the tx would be kept in mempool, but we cannot see this anymore). Else only the owner would be able to spend the tx...

There is probably more to it, I leave it to the experts to decode the remaining part :-)

2
  • wow that's a lot of work, thanks! I guess the off-by-one problem is probably at OP_PICK which should pick one-element deeper down the stack(the (n+1)th element after popping out n?).
    – Rod Lin
    Commented Feb 25, 2018 at 11:36
  • Yes, I see the off by one also at the OP_PICK, it‘s a bit speculation, cause we don‘t know the original intent. The effort was not too much, I have a set of shell scripts. But aligning it, so it fits here in bitcoin.SE was a major piece of work :-) Commented Feb 25, 2018 at 12:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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