Skip to content

Instantly share code, notes, and snippets.

@davecgh
Created January 15, 2019 11:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save davecgh/4eb33e4e080eb34ab5532f7d7a0a0776 to your computer and use it in GitHub Desktop.
Save davecgh/4eb33e4e080eb34ab5532f7d7a0a0776 to your computer and use it in GitHub Desktop.
Sample code to create and sign an offline p2sh 2-of-3 multisig transaction given WIF-encoded private keys
package main
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/dcrec/secp256k1"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/mempool"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
)
func panicOnErr(err error) {
if err != nil {
panic(err)
}
}
func hexToBytes(hexStr string) []byte {
b, err := hex.DecodeString(hexStr)
panicOnErr(err)
return b
}
func main() {
// Private keys.
wif1, err := dcrutil.DecodeWIF("PmQeFWS7F4Geh7ShLrfNTVeWGq6yXUeHmK9eUecNbfD97TZaixNxT")
panicOnErr(err)
wif2, err := dcrutil.DecodeWIF("PmQdwa6Bz94wc9b7PKyCmFcJoTsqx5vwLwQQ9kvybaJa3b9gkHh2S")
panicOnErr(err)
wif3, err := dcrutil.DecodeWIF("PmQfCoLv8vmjyc3bK6xKAtvarGVJ2nyFUH28wqt3ASxZzZN4C92x2")
panicOnErr(err)
// Create addresses for generating the script.
pubkey1Addr, err := dcrutil.NewAddressSecpPubKey(wif1.SerializePubKey(), &chaincfg.MainNetParams)
panicOnErr(err)
pubkey2Addr, err := dcrutil.NewAddressSecpPubKey(wif2.SerializePubKey(), &chaincfg.MainNetParams)
panicOnErr(err)
pubkey3Addr, err := dcrutil.NewAddressSecpPubKey(wif3.SerializePubKey(), &chaincfg.MainNetParams)
panicOnErr(err)
// Generate 2-of-3 multisig script.
redeemScript, err := txscript.MultiSigScript([]*dcrutil.AddressSecpPubKey{
pubkey1Addr, pubkey2Addr, pubkey3Addr}, 2)
fmt.Printf("redeemScript: %x\n", redeemScript)
var tx wire.MsgTx
unsignedTxBytes := hexToBytes("0100000001d1bab9f5cb33e74e2f49d4443fefdaf487e4b56887f8b2dea1d8532aa5a555530100000000ffffffff0138199a0000000000000017a914a3c79cf163c07ac6eb15e553ee177936e4e777da87000000000000000001ffffffffffffffff00000000ffffffff00")
panicOnErr(tx.FromBytes(unsignedTxBytes))
// Generate the p2sh public key script.
pkScript, err := txscript.PayToScriptHashScript(dcrutil.Hash160(redeemScript))
panicOnErr(err)
fmt.Printf("pkScript: %x\n", pkScript)
// Calculate the signature hash for signing all inputs and outputs.
sigHashType := txscript.SigHashAll
hash, err := txscript.CalcSignatureHash(redeemScript, sigHashType, &tx, 0, nil)
panicOnErr(err)
fmt.Printf("Signature hash: %x\n", hash)
// Generate a signature.
privKey1 := wif1.PrivKey.(*secp256k1.PrivateKey)
sig1, err := privKey1.Sign(hash)
panicOnErr(err)
sig1WithHT := append(sig1.Serialize(), byte(sigHashType))
fmt.Printf("Signature 1: %x\n", sig1WithHT)
// Generate a second signature.
privKey2 := wif2.PrivKey.(*secp256k1.PrivateKey)
sig2, err := privKey2.Sign(hash)
panicOnErr(err)
sig2WithHT := append(sig2.Serialize(), byte(sigHashType))
fmt.Printf("Signature 2: %x\n", sig2WithHT)
// Generate the final signature script and update the transaction with it.
sigScript, err := txscript.NewScriptBuilder().AddData(sig1WithHT).AddData(sig2WithHT).AddData(redeemScript).Script()
panicOnErr(err)
fmt.Printf("sigScript: %x\n", sigScript)
tx.TxIn[0].SignatureScript = sigScript
// Dump the final serialized signed tx.
var serializedSignedTx bytes.Buffer
panicOnErr(tx.BtcEncode(&serializedSignedTx, 0))
fmt.Printf("signed tx: %x\n", serializedSignedTx.Bytes())
// Verify the transaction is signed correctly by executing its script.
vm, err := txscript.NewEngine(pkScript, &tx, 0, mempool.BaseStandardVerifyFlags|txscript.ScriptVerifySHA256, 0, nil)
panicOnErr(err)
panicOnErr(vm.Execute())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment