From 46963d63656dcb4da2e118d9c40ab7e8749f2e49 Mon Sep 17 00:00:00 2001 From: Javed Khan Date: Mon, 27 Feb 2023 15:18:12 -0800 Subject: [PATCH] files --- relayer.go | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++++ write.go | 175 ------------------------------------------------- 2 files changed, 187 insertions(+), 175 deletions(-) create mode 100644 relayer.go diff --git a/relayer.go b/relayer.go new file mode 100644 index 0000000..abeef61 --- /dev/null +++ b/relayer.go @@ -0,0 +1,187 @@ +package rollkitbtc + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +// payToTaprootScript creates a pk script for a pay-to-taproot output key. +func payToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) { + return txscript.NewScriptBuilder(). + AddOp(txscript.OP_1). + AddData(schnorr.SerializePubKey(taprootKey)). + Script() +} + +// Relayer is a bitcoin client wrapper which provides reader and writer methods +// to write binary blobs to the blockchain. +type Relayer struct { + client *rpcclient.Client +} + +// Close shuts down the client. +func (r Relayer) Close() { + r.client.Shutdown() +} + +// commitTx commits an output to the given taproot address, such that the +// output is only spendable by posting the embedded data on chain, as part of +// the script satisfying the tapscript spend path that commits to the data. It +// returns the hash of the commit transaction and error, if any. +func (r Relayer) commitTx(addr string) (*chainhash.Hash, error) { + // Create a transaction that sends 0.001 BTC to the given address. + address, err := btcutil.DecodeAddress(addr, &chaincfg.RegressionNetParams) + if err != nil { + return nil, fmt.Errorf("error decoding recipient address: %v", err) + } + + amount, err := btcutil.NewAmount(0.001) + if err != nil { + return nil, fmt.Errorf("error creating new amount: %v", err) + } + + hash, err := r.client.SendToAddress(address, amount) + if err != nil { + return nil, fmt.Errorf("error sending to address: %v", err) + } + + return hash, nil +} + +// revealTx spends the output from the commit transaction and as part of the +// script satisfying the tapscript spend path, posts the embedded data on +// chain. It returns the hash of the reveal transaction and error, if any. +func (r Relayer) revealTx(embeddedData []byte, commitHash *chainhash.Hash) (*chainhash.Hash, error) { + rawCommitTx, err := r.client.GetRawTransaction(commitHash) + if err != nil { + return nil, fmt.Errorf("error getting raw commit tx: %v", err) + } + + // TODO: use a better way to find our output + var commitIndex int + var commitOutput *wire.TxOut + for i, out := range rawCommitTx.MsgTx().TxOut { + if out.Value == 100000 { + commitIndex = i + commitOutput = out + break + } + } + + privKey, err := btcutil.DecodeWIF(bobPrivateKey) + if err != nil { + return nil, fmt.Errorf("error decoding bob private key: %v", err) + } + + pubKey := privKey.PrivKey.PubKey() + + internalPrivKey, err := btcutil.DecodeWIF(internalPrivateKey) + if err != nil { + return nil, fmt.Errorf("error decoding internal private key: %v", err) + } + + internalPubKey := internalPrivKey.PrivKey.PubKey() + + // Our script will be a simple OP_DROP OP_CHECKSIG as the + // sole leaf of a tapscript tree. + builder := txscript.NewScriptBuilder() + builder.AddData(embeddedData) + builder.AddOp(txscript.OP_DROP) + builder.AddData(schnorr.SerializePubKey(pubKey)) + builder.AddOp(txscript.OP_CHECKSIG) + pkScript, err := builder.Script() + if err != nil { + return nil, fmt.Errorf("error building script: %v", err) + } + + tapLeaf := txscript.NewBaseTapLeaf(pkScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + + ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock( + internalPubKey, + ) + + tapScriptRootHash := tapScriptTree.RootNode.TapHash() + outputKey := txscript.ComputeTaprootOutputKey( + internalPubKey, tapScriptRootHash[:], + ) + p2trScript, err := payToTaprootScript(outputKey) + if err != nil { + return nil, fmt.Errorf("error building p2tr script: %v", err) + } + + tx := wire.NewMsgTx(2) + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: *rawCommitTx.Hash(), + Index: uint32(commitIndex), + }, + }) + txOut := &wire.TxOut{ + Value: 1e3, PkScript: p2trScript, + } + tx.AddTxOut(txOut) + + inputFetcher := txscript.NewCannedPrevOutputFetcher( + commitOutput.PkScript, + commitOutput.Value, + ) + sigHashes := txscript.NewTxSigHashes(tx, inputFetcher) + + sig, err := txscript.RawTxInTapscriptSignature( + tx, sigHashes, 0, txOut.Value, + txOut.PkScript, tapLeaf, txscript.SigHashDefault, + privKey.PrivKey, + ) + + if err != nil { + return nil, fmt.Errorf("error signing tapscript: %v", err) + } + + // Now that we have the sig, we'll make a valid witness + // including the control block. + ctrlBlockBytes, err := ctrlBlock.ToBytes() + if err != nil { + return nil, fmt.Errorf("error including control block: %v", err) + } + tx.TxIn[0].Witness = wire.TxWitness{ + sig, pkScript, ctrlBlockBytes, + } + + hash, err := r.client.SendRawTransaction(tx, false) + if err != nil { + return nil, fmt.Errorf("error sending reveal transaction: %v", err) + } + return hash, nil +} + +// NewRelayer returns a new relayer. It can error if there's an RPC connection +// error with the connection config. +func NewRelayer() (*Relayer, error) { + // Set up the connection to the btcd RPC server. + // NOTE: for testing bitcoind can be used in regtest with the following params - + // bitcoind -chain=regtest -rpcport=18332 -rpcuser=rpcuser -rpcpassword=rpcpass -fallbackfee=0.000001 + connCfg := &rpcclient.ConnConfig{ + Host: "localhost:18332", + User: "rpcuser", + Pass: "rpcpass", + HTTPPostMode: true, + DisableTLS: true, + } + client, err := rpcclient.New(connCfg, nil) + if err != nil { + return nil, fmt.Errorf("error creating btcd RPC client: %v", err) + } + return &Relayer{ + client: client, + }, nil +} + diff --git a/write.go b/write.go index 0cbc237..aafce5a 100644 --- a/write.go +++ b/write.go @@ -3,14 +3,11 @@ package rollkitbtc import ( "fmt" - "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" ) // Sample data and keys for testing. @@ -21,178 +18,6 @@ var ( internalPrivateKey = "5JGgKfRy6vEcWBpLJV5FXUfMGNXzvdWzQHUM1rVLEUJfvZUSwvS" ) -// Relayer is a bitcoin client wrapper which provides reader and writer methods -// to write binary blobs to the blockchain. -type Relayer struct { - client *rpcclient.Client -} - -// Close shuts down the client. -func (r Relayer) Close() { - r.client.Shutdown() -} - -// NewRelayer returns a new relayer. It can error if there's an RPC connection -// error with the connection config. -func NewRelayer() (*Relayer, error) { - // Set up the connection to the btcd RPC server. - // NOTE: for testing bitcoind can be used in regtest with the following params - - // bitcoind -chain=regtest -rpcport=18332 -rpcuser=rpcuser -rpcpassword=rpcpass -fallbackfee=0.000001 - connCfg := &rpcclient.ConnConfig{ - Host: "localhost:18332", - User: "rpcuser", - Pass: "rpcpass", - HTTPPostMode: true, - DisableTLS: true, - } - client, err := rpcclient.New(connCfg, nil) - if err != nil { - return nil, fmt.Errorf("error creating btcd RPC client: %v", err) - } - return &Relayer{ - client: client, - }, nil -} - -// commitTx commits an output to the given taproot address, such that the -// output is only spendable by posting the embedded data on chain, as part of -// the script satisfying the tapscript spend path that commits to the data. It -// returns the hash of the commit transaction and error, if any. -func (r Relayer) commitTx(addr string) (*chainhash.Hash, error) { - // Create a transaction that sends 0.001 BTC to the given address. - address, err := btcutil.DecodeAddress(addr, &chaincfg.RegressionNetParams) - if err != nil { - return nil, fmt.Errorf("error decoding recipient address: %v", err) - } - - amount, err := btcutil.NewAmount(0.001) - if err != nil { - return nil, fmt.Errorf("error creating new amount: %v", err) - } - - hash, err := r.client.SendToAddress(address, amount) - if err != nil { - return nil, fmt.Errorf("error sending to address: %v", err) - } - - return hash, nil -} - -// revealTx spends the output from the commit transaction and as part of the -// script satisfying the tapscript spend path, posts the embedded data on -// chain. It returns the hash of the reveal transaction and error, if any. -func (r Relayer) revealTx(embeddedData []byte, commitHash *chainhash.Hash) (*chainhash.Hash, error) { - rawCommitTx, err := r.client.GetRawTransaction(commitHash) - if err != nil { - return nil, fmt.Errorf("error getting raw commit tx: %v", err) - } - - // TODO: use a better way to find our output - var commitIndex int - var commitOutput *wire.TxOut - for i, out := range rawCommitTx.MsgTx().TxOut { - if out.Value == 100000 { - commitIndex = i - commitOutput = out - break - } - } - - privKey, err := btcutil.DecodeWIF(bobPrivateKey) - if err != nil { - return nil, fmt.Errorf("error decoding bob private key: %v", err) - } - - pubKey := privKey.PrivKey.PubKey() - - internalPrivKey, err := btcutil.DecodeWIF(internalPrivateKey) - if err != nil { - return nil, fmt.Errorf("error decoding internal private key: %v", err) - } - - internalPubKey := internalPrivKey.PrivKey.PubKey() - - // Our script will be a simple OP_DROP OP_CHECKSIG as the - // sole leaf of a tapscript tree. - builder := txscript.NewScriptBuilder() - builder.AddData(embeddedData) - builder.AddOp(txscript.OP_DROP) - builder.AddData(schnorr.SerializePubKey(pubKey)) - builder.AddOp(txscript.OP_CHECKSIG) - pkScript, err := builder.Script() - if err != nil { - return nil, fmt.Errorf("error building script: %v", err) - } - - tapLeaf := txscript.NewBaseTapLeaf(pkScript) - tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) - - ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock( - internalPubKey, - ) - - tapScriptRootHash := tapScriptTree.RootNode.TapHash() - outputKey := txscript.ComputeTaprootOutputKey( - internalPubKey, tapScriptRootHash[:], - ) - p2trScript, err := payToTaprootScript(outputKey) - if err != nil { - return nil, fmt.Errorf("error building p2tr script: %v", err) - } - - tx := wire.NewMsgTx(2) - tx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: wire.OutPoint{ - Hash: *rawCommitTx.Hash(), - Index: uint32(commitIndex), - }, - }) - txOut := &wire.TxOut{ - Value: 1e3, PkScript: p2trScript, - } - tx.AddTxOut(txOut) - - inputFetcher := txscript.NewCannedPrevOutputFetcher( - commitOutput.PkScript, - commitOutput.Value, - ) - sigHashes := txscript.NewTxSigHashes(tx, inputFetcher) - - sig, err := txscript.RawTxInTapscriptSignature( - tx, sigHashes, 0, txOut.Value, - txOut.PkScript, tapLeaf, txscript.SigHashDefault, - privKey.PrivKey, - ) - - if err != nil { - return nil, fmt.Errorf("error signing tapscript: %v", err) - } - - // Now that we have the sig, we'll make a valid witness - // including the control block. - ctrlBlockBytes, err := ctrlBlock.ToBytes() - if err != nil { - return nil, fmt.Errorf("error including control block: %v", err) - } - tx.TxIn[0].Witness = wire.TxWitness{ - sig, pkScript, ctrlBlockBytes, - } - - hash, err := r.client.SendRawTransaction(tx, false) - if err != nil { - return nil, fmt.Errorf("error sending reveal transaction: %v", err) - } - return hash, nil -} - -// payToTaprootScript creates a pk script for a pay-to-taproot output key. -func payToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) { - return txscript.NewScriptBuilder(). - AddOp(txscript.OP_1). - AddData(schnorr.SerializePubKey(taprootKey)). - Script() -} - // createTaprootAddress returns an address committing to a Taproot script with // a single leaf containing the spend path with the script: // OP_DROP OP_CHECKSIG