Script. OP_PICK/OP_ROLL

1

OP_PICK should take the nth element from the back of the stack and take it on top: for input xn ... x2 x1 x0
gives xn ... x2 x1 x0 xn

I have used it in some scripts, but I've noticed some strange behaviour.

For instance, using the tool in: http://webbtc.com/script an input script 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 and an output script 19 OP_PICK produces an error. However 18 OP_PICK works. For 25 elements in the stack it should work in both cases. The problem doesn't seem to be the number 19. If I add one input
1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 the scripts works fine.

I have tried looking at the source code, but in https://github.com/bitcoin/bitcoin/blob/ce56f5621a94dcc2159ebe57e43da727eab18e6c/src/script/interpreter.cpp around line 551 I find:

            case OP_OVER:
            {
                // (x1 x2 -- x1 x2 x1)
                if (stack.size() < 2)
                    return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
                valtype vch = stacktop(-2);
                stack.push_back(vch);
            }
            break;

            case OP_PICK:
            case OP_ROLL:
            {
                // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn)
                // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn)
                if (stack.size() < 2)
                    return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
                int n = CScriptNum(stacktop(-1), fRequireMinimal).getint();
                popstack(stack);

I'm not really sure how OP_PICK is implemented. Can anyone point me to the code for OP_PICK or explain the errors?

halftimepad

Posted 2015-06-21T00:14:56.143

Reputation: 1 218

2Is that 18 in decimal, or 18 in hex?Nick ODell 2015-06-21T00:37:37.143

@NickODell: I think you've got it. It appears that site inteprets "18" to be hex.Nate Eldredge 2015-06-21T02:50:48.173

It really seems to be taking numbers above 16 as hexadecimal (with OP_16 the highest integer you can directly push to the stack). Still trying to figure out the whole format of http://webbtc.com/script and the minimaldata verification to work around the problem.

halftimepad 2015-06-21T11:36:43.443

Answers

1

Here's that source code, annotated. Each annotation refers to the code after it.


This means that this part of the code handles both OP_PICK and OP_ROLL. This is because OP_PICK and OP_ROLL are very similar. The only difference is that PICK copies and ROLL moves.

            case OP_PICK:
            case OP_ROLL:
            {

These are two examples of the stack before and after. This is sometimes called a 'precondition' and a 'postcondition.'

This one documents OP_PICK.

Precondition: xn ... x2 x1 x0 n

Postcondition: xn ... x2 x1 x0 xn

                // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn)

This one documents OP_ROLL.

Precondition: xn ... x2 x1 x0 n

Postcondition: ... x2 x1 x0 xn (Note the lack of xn at the beginning.

                // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn)

You need to have at least two elements on the stack, or this operation makes no sense.

                if (stack.size() < 2)
                    return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

Copy (but do not remove) the top element on the stack. Interpret it as a number. Convert it to a 32-bit integer, so it's easier to work with.

                int n = CScriptNum(stacktop(-1), fRequireMinimal).getint();

Now remove the top element on the stack. (Which should be n, remember.)

                popstack(stack);

If n is negative, fail. If n is larger than the stack, fail. (This is where I think your script is having an error.)

                if (n < 0 || n >= (int)stack.size())
                    return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

Copy the value we're after. Remember, 1 2 3 0 OP_PICK should get the element just below n, 3. Also, stacktop(-1) refers to the top. So therefore, we need to invert n and subtract 1.

                valtype vch = stacktop(-n-1);

Remember, OP_ROLL deletes the element from where it was originally. This code is not executed for OP_PICK.

                if (opcode == OP_ROLL)
                    stack.erase(stack.end()-n-1);

Now add the element that we just got back on the top of the stack.

                stack.push_back(vch);

We're done handling this case.

            }
            break;

Does that help?

Nick ODell

Posted 2015-06-21T00:14:56.143

Reputation: 26 536

It really does. I thought either it did nothing or used exactly the same code as OP_ROLL. The commented code around if (opcode == OP_ROLL) has been the key for me.halftimepad 2015-06-21T11:29:39.210