Skip to content

Latest commit

 

History

History
207 lines (159 loc) · 11.2 KB

08_2_Building_the_Structure_of_P2SH.md

File metadata and controls

207 lines (159 loc) · 11.2 KB

8.2: Building the Structure of P2SH

NOTE: This is a draft in progress, so that I can get some feedback from early reviewers. It is not yet ready for learning.

In the previous section we overviewed the theory of how to create P2SH transactions to hold Bitcoin Scripts. The actual practice of doing so is much more difficult, but for the sake of completeness, we're going to look at it here. This is probably not something you'd ever do without an API, so if it gets to intimidating, be aware that we'll returning to pristine, high-level Scripts in a moment.

Create a Locking Script

Any P2SH transaction starts with a locking script. This is the subject of chapters 7 and 9-10. You can use any of the Bitcoin Script methods described therein to create any sort of locking script, as long as the resulting serialized redeemScript is 520 bytes or less.

Why are P2SH scripts limited to 520 bytes? As with many things in Bitcoin, the answer is backward compatibility: new functionality has to constantly be built within the old constraints of the system. Is this case, 520 bytes is the maximum that can be pushed onto the stack at once. Since the whole redeemScript is pushed onto the stack as part of the redemption process, it hits that limit.

Serialize a Locking Script

After you create a locking script, you need to serialize it before it can be input into Bitcoin. This is a two-part process. First, you must turn it into hexcode, then you must transform that hex into binary.

Create the Hex Code

Creating the hexcode that is necessary to serialize a script is both a simple translation and something that is complex enough that it goes beyond any shell script that you're likely to write. This step is one of the main reasons that you need an API to create P2SH transactions.

You create hexcode by stepping through your locking script and turning each element into one-byte hex command, possibly followed by additional data, per the guide at the Bitcoin Wiki Script page:

  • Operators are translated to the matching byte for that opcode
  • The constants 1-16 are translated to opcodes 0x51 to 0x61 (OP_1 to OP_16)
  • The constant -1 is translate to opcode 0x4f (OP_1NEGATE)
  • Other constants are preceded by opcodes 0x01 to 0x4e (OP_PUSHDATA, with the number specifying how many bytes to push)
    • Integers are translated into hex using little-endian signed-magnitude notation

Translate Integers

The integers are the most troublesome part of a locking-script translation.

First, you should verify that your number falls between -2147483647 and 2147483647, the range of four-byte integers when the most significant byte is used for signing.

Second, you need to translate the decimal value into hexidecimal and pad it out to an even number of digits. This can be done with the printf command:

$ integer=1546288031
$ hex=$(printf '%08x\n' $integer | sed 's/^\(00\)*//')
$ echo $hex
5c2a7b9f

Third, you need to add an additional leading byte of 00 if the top digit is "8" or greater, so that the number is not interpreted as negative.

$ hexfirst=$(echo $hex | cut -c1)
$ [[ 0x$hexfirst -gt 0x7 ]] && hex="00"$hex

Fourth, you need to translate the hex from big-endian (least significant byte last) to little-endian (least significant byte first). You can do this with the tac command:

$ lehex=$(echo $hex | tac -rs .. | echo "$(tr -d '\n')")
$ echo $lehex
9f7b2a5c

In addition, you always need to know the size of any data that you put on the stack, so that you can precede it with the proper opcode. You can just remember that every two hexidecimal characters is one byte. Or, you can use echo -n piped to wc -c, and divide that in half:

$ echo -n $lehex | wc -c | awk '{print $1/2}'
4

With that whole rigamarole, you'd know that you could translate the integer 1546288031 into an 04 opcode (to push four bytes onto the stack) followed by 9f7b2a5c (the little-endian hex representation of 1546288031).

If you instead had a negative number, you would need to (1) do your calculations on the absolute value of the number, then (2) bitwise-or 0x80 to your final, little-endian result. For example, 9f7b2a5c, which is 1546288031, would become 9f7b2adc, which is -1546288031:

$ neglehex=$(printf '%x\n' $((0x$lehex | 0x80)))
$ echo $neglehex
9f7b2adc

(There's a script at the end to automate all of this.)

Transform the Hex to Binary

To complete your serialization, you translate the hexcode into binary. On the command line, this just requires a simple invocation of xxd -r -p. However, you probably want to do that as part of a a single pipe that will also hash the script ...

Analyze a P2SH Multisig

To better understand this process, we will reverse-engineer the P2SH multisig that we created in §6.1: Sending a Transaction to a Multisig. Take a look at the redeemScript that you used, which you now know is the hex-serialized version of the locking script:

52210307fd375ed7cced0f50723e3e1a97bbe7ccff7318c815df4e99a59bc94dbcd819210367c4f666f18279009c941e57fab3e42653c6553e5ca092c104d1db279e328a2852ae

You can translate this back to Script by hand using the Bitcoin Wiki Script page as a reference. Just look at one byte (two hex characters) of data at a time, unless you're told to look at more by an OP_PUSHDATA command (an opcode in the range of 0x01 to 0x4e).

The whole Script will break apart as follows:

52 / 21 / 0307fd375ed7cced0f50723e3e1a97bbe7ccff7318c815df4e99a59bc94dbcd819 / 21 / 0367c4f666f18279009c941e57fab3e42653c6553e5ca092c104d1db279e328a28 / 52 / ae

Here's what the individual parts mean:

  • 0x52 = OP_2
  • 0x21 = OP_PUSHDATA 33 bytes (hex: 0x21)
  • 0x0307fd375ed7cced0f50723e3e1a97bbe7ccff7318c815df4e99a59bc94dbcd819 = the next 33 bytes (public-key hash)
  • 0x21 = OP_PUSHDATA 33 bytes (hex: 0x21)
  • 0x0367c4f666f18279009c941e57fab3e42653c6553e5ca092c104d1db279e328a28 = the next 33 bytes (public-key hash)
  • 0x52 = OP_2
  • 0xae = OP_CHECKMULTISIG

In other words, that redeemScript was a translation of of 2 0307fd375ed7cced0f50723e3e1a97bbe7ccff7318c815df4e99a59bc94dbcd819 0367c4f666f18279009c941e57fab3e42653c6553e5ca092c104d1db279e328a28 2 OP_CHECKMULTISIG. We'll return to this script in §8.4: Scripting a Multisig when we detail exactly how multisigs work within the P2SH paradigm.

If you'd like a mechanical hand with this sort of translation in the future, you can use bitcoin-cli decodescript:

$ bitcoin-cli -named decodescript hexstring=52210307fd375ed7cced0f50723e3e1a97bbe7ccff7318c815df4e99a59bc94dbcd819210367c4f666f18279009c941e57fab3e42653c6553e5ca092c104d1db279e328a2852ae
{
  "asm": "2 0307fd375ed7cced0f50723e3e1a97bbe7ccff7318c815df4e99a59bc94dbcd819 0367c4f666f18279009c941e57fab3e42653c6553e5ca092c104d1db279e328a28 2 OP_CHECKMULTISIG",
  "reqSigs": 2,
  "type": "multisig",
  "addresses": [
    "mg7YqyvK8HUFvpgZ5iYTfZ5vjfaJWnNTd9", 
    "mfduLxpR6Bq1ARctV2TauhetWwqnqH1vYS"
  ],
  "p2sh": "2NAGfA4nW6nrZkD5je8tSiAcYB9xL2xYMCz"
}

It's especially helpful for checking your work when you're serializing.

Also consider the Python Transaction Script Compiler, which translates back and forth.

Hash a Serialized Script

After you've created a locking script and serialized it, the third step in creating a P2SH transaction is to hash the locking script. As previously noted, a 20-byte OP_HASH160 hash is created through a combination of a SHA-256 hash and a RIPEMD-160 hash. Hashing a serialized script thus takes two commands: openssl dgst -sha256 -binary does the SHA-256 hash and outputs a binary to be sent through the pipe, then openssl dgst -rmd160 takes that binary stream, does a RIPEMD-160 hash, and finally outputs a human-readable hexcode.

Here's the whole pipe, including the previous transformation of the hex-serialized script into binary:

$ redeemScript="52210307fd375ed7cced0f50723e3e1a97bbe7ccff7318c815df4e99a59bc94dbcd819210367c4f666f18279009c941e57fab3e42653c6553e5ca092c104d1db279e328a2852ae"
$ echo -n $redeemScript | xxd -r -p | openssl dgst -sha256 -binary | openssl dgst -rmd160
(stdin)= babf9063cee8ab6e9334f95f6d4e9148d0e551c2

Create a P2SH Transaction

Creating your 20-bit hash just gives you the hash at the center of a P2SH locking script. You still need to put it together with the other opcodes that create a standard P2SH transaction: OP_HASH160 babf9063cee8ab6e9334f95f6d4e9148d0e551c2 OP_EQUAL.

Depending on your API, you might be able to enter this as an asm-style scriptPubKey for your transaction, or you might have to translate it to hex code as well. If you have to translate, use the same methods described above for "Creating the Hex Code", resulting in a914babf9063cee8ab6e9334f95f6d4e9148d0e551c287.

Note that the hex scriptPubKey for P2SH Script transaction will always start with an a914, which is the OP_HASH160 followed by an OP_PUSHDATA of 20 bytes (hex: 0x14); and it will always end with a 87, which is an OP_EQUAL. So all you have to do is put your hashed redeem script in between those numbers.

Summary: Understanding the Foundation of P2SH

Actually creating the P2SH locking script dives further into the guts of Bitcoin than you've ever gone before. Though it's helpful to know how all of this works at a very low level, it's most likely that you'll have an API taking care of all of the heavy-lifting for you. Your task will simply be to create the Bitcoin Script to do the locking ... which is the main topic of chapters 7 and 9-10.

What's Next?

Continue "Embedding Bitcoin Scripts" with §8.3: Running a Bitcoin Script with P2SH.

Appendix: The Integer Conversion Script

The following script collects the complete methodology for changing an integer between -2147483647 and 2147483647 to a little-endian signed-magnitude representation in hex:

WARNING: This script has not been robustly checked. If you are going to use it to create real locking scripts you should make sure to double-check and test your results.

file: integer2lehex.sh
#!/bin/bash

if [ -z $1 ];
then
    echo "You must include an integer as an argument.";
    exit;
fi

if (( $1 > "2147483647" )) || (( $1 < "-2147483647" ));
then
    echo "Your number ($1) must be between -2147483647 and 2147483647";
    exit;
fi

if [ $1 -lt 0 ];
then
    integer=$(echo $((-$1)));
    negative=1;
else
    integer=$1;
    negative=0;
fi

hex=$(printf '%08x\n' $integer | sed 's/^\(00\)*//');

hexfirst=$(echo $hex | cut -c1)
[[ 0x$hexfirst -gt 0x7 ]] && hex="00"$hex

lehex=$(echo $hex | tac -rs .. | echo "$(tr -d '\n')");

if [ "$negative" -eq "1" ];
then
   lehex=$(printf '%x\n' $((0x$lehex | 0x80)))
fi

size=$(echo -n $lehex | wc -c | awk '{print $1/2}');
hexcodeprefix=$(printf '%02x\n' $size);

echo "Integer: $1";
echo "LE Hex: $lehex";
echo "Length: $size bytes";
echo "Hexcode: $hexcodeprefix$lehex";

Be sure the permissions on the script are right:

$ chmod 755 integer2lehex.sh

You can then run the script as follows:

$ ./integer2lehex.sh 1546288031
Integer: 1546288031
LE Hex: 9f7b2a5c
Length: 4 bytes
Hexcode: 049f7b2a5c

$ ./integer2lehex.sh -1546288031
Integer: -1546288031
LE Hex: 9f7b2adc
Length: 4 bytes
Hexcode: 049f7b2adc