-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
187 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <embedded-data> 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 | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters