mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-01-12 22:43:22 +08:00
feat: add bip322 pkg
This commit is contained in:
191
pkg/bip322/bip322.go
Normal file
191
pkg/bip322/bip322.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package bip322
|
||||||
|
|
||||||
|
// This package is forked from https://github.com/unisat-wallet/libbrc20-indexer/blob/v1.1.0/utils/bip322/verify.go,
|
||||||
|
// with a few modifications to make the interface more friendly with Gaze types.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSha256(data []byte) (hash []byte) {
|
||||||
|
sha := sha256.New()
|
||||||
|
sha.Write(data[:])
|
||||||
|
hash = sha.Sum(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTagSha256(data []byte) (hash []byte) {
|
||||||
|
tag := []byte("BIP0322-signed-message")
|
||||||
|
hashTag := GetSha256(tag)
|
||||||
|
var msg []byte
|
||||||
|
msg = append(msg, hashTag...)
|
||||||
|
msg = append(msg, hashTag...)
|
||||||
|
msg = append(msg, data...)
|
||||||
|
return GetSha256(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrepareTx(pkScript []byte, message string) (toSign *wire.MsgTx, err error) {
|
||||||
|
// Create a new transaction to spend
|
||||||
|
toSpend := wire.NewMsgTx(0)
|
||||||
|
|
||||||
|
// Decode the message hash
|
||||||
|
messageHash := GetTagSha256([]byte(message))
|
||||||
|
|
||||||
|
// Create the script for to_spend
|
||||||
|
builder := txscript.NewScriptBuilder()
|
||||||
|
builder.AddOp(txscript.OP_0)
|
||||||
|
builder.AddData(messageHash)
|
||||||
|
scriptSig, err := builder.Script()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a TxIn with the outpoint 000...000:FFFFFFFF
|
||||||
|
prevOutHash, _ := chainhash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
prevOut := wire.NewOutPoint(prevOutHash, wire.MaxPrevOutIndex)
|
||||||
|
txIn := wire.NewTxIn(prevOut, scriptSig, nil)
|
||||||
|
txIn.Sequence = 0
|
||||||
|
|
||||||
|
toSpend.AddTxIn(txIn)
|
||||||
|
toSpend.AddTxOut(wire.NewTxOut(0, pkScript))
|
||||||
|
|
||||||
|
// Create a transaction for to_sign
|
||||||
|
toSign = wire.NewMsgTx(0)
|
||||||
|
hash := toSpend.TxHash()
|
||||||
|
|
||||||
|
prevOutSpend := wire.NewOutPoint((*chainhash.Hash)(hash.CloneBytes()), 0)
|
||||||
|
|
||||||
|
txSignIn := wire.NewTxIn(prevOutSpend, nil, nil)
|
||||||
|
txSignIn.Sequence = 0
|
||||||
|
toSign.AddTxIn(txSignIn)
|
||||||
|
|
||||||
|
// Create the script for to_sign
|
||||||
|
builderPk := txscript.NewScriptBuilder()
|
||||||
|
builderPk.AddOp(txscript.OP_RETURN)
|
||||||
|
scriptPk, err := builderPk.Script()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
toSign.AddTxOut(wire.NewTxOut(0, scriptPk))
|
||||||
|
return toSign, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyMessage(address *btcutils.Address, signature []byte, message string) bool {
|
||||||
|
if len(signature) == 0 {
|
||||||
|
// empty signature is invalid
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// BIP322 signature format is the serialized witness of the toSign transaction.
|
||||||
|
// [0x02] [SIGNATURE_LEN, ...(signature that go into witness[0])] [PUBLIC_KEY_LEN, ...(public key that was used to sign the message, go to witness[1])]
|
||||||
|
witness, err := DeserializeWitnessSignature(signature)
|
||||||
|
if err != nil {
|
||||||
|
// invalid signature
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return verifySignatureWitness(witness, address.ScriptPubKey(), message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifySignatureWitness
|
||||||
|
// signature: 64B, pkScript: 33B, message: any
|
||||||
|
func verifySignatureWitness(witness wire.TxWitness, pkScript []byte, message string) bool {
|
||||||
|
toSign, err := PrepareTx(pkScript, message)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
toSign.TxIn[0].Witness = witness
|
||||||
|
prevFetcher := txscript.NewCannedPrevOutputFetcher(
|
||||||
|
pkScript, 0,
|
||||||
|
)
|
||||||
|
hashCache := txscript.NewTxSigHashes(toSign, prevFetcher)
|
||||||
|
vm, err := txscript.NewEngine(pkScript, toSign, 0, txscript.StandardVerifyFlags, nil, hashCache, 0, prevFetcher)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := vm.Execute(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func SignMessage(privateKey *btcec.PrivateKey, address *btcutils.Address, message string) ([]byte, error) {
|
||||||
|
var witness wire.TxWitness
|
||||||
|
var err error
|
||||||
|
switch address.Type() {
|
||||||
|
case btcutils.AddressP2TR:
|
||||||
|
witness, _, err = SignSignatureTaproot(privateKey, message)
|
||||||
|
case btcutils.AddressP2WPKH:
|
||||||
|
witness, _, err = SignSignatureP2WPKH(privateKey, message)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
signature, err := SerializeWitnessSignature(witness)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SignSignatureTaproot(privKey *btcec.PrivateKey, message string) (witness wire.TxWitness, pkScript []byte, err error) {
|
||||||
|
pubKey := txscript.ComputeTaprootKeyNoScript(privKey.PubKey())
|
||||||
|
|
||||||
|
pkScript, err = PayToTaprootScript(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toSign, err := PrepareTx(pkScript, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prevFetcher := txscript.NewCannedPrevOutputFetcher(
|
||||||
|
pkScript, 0,
|
||||||
|
)
|
||||||
|
sigHashes := txscript.NewTxSigHashes(toSign, prevFetcher)
|
||||||
|
|
||||||
|
witness, err = txscript.TaprootWitnessSignature(
|
||||||
|
toSign, sigHashes, 0, 0, pkScript,
|
||||||
|
txscript.SigHashDefault, privKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return witness, pkScript, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SignSignatureP2WPKH(privKey *btcec.PrivateKey, message string) (witness wire.TxWitness, pkScript []byte, err error) {
|
||||||
|
pubKey := privKey.PubKey()
|
||||||
|
pkScript, err = PayToWitnessScript(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toSign, err := PrepareTx(pkScript, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prevFetcher := txscript.NewCannedPrevOutputFetcher(
|
||||||
|
pkScript, 0,
|
||||||
|
)
|
||||||
|
sigHashes := txscript.NewTxSigHashes(toSign, prevFetcher)
|
||||||
|
|
||||||
|
witness, err = txscript.WitnessSignature(toSign, sigHashes,
|
||||||
|
0, 0, pkScript, txscript.SigHashAll,
|
||||||
|
privKey, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return witness, pkScript, nil
|
||||||
|
}
|
||||||
145
pkg/bip322/bip322_test.go
Normal file
145
pkg/bip322/bip322_test.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package bip322
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVerifyMessage(t *testing.T) {
|
||||||
|
type testcase struct {
|
||||||
|
Address string
|
||||||
|
Message string
|
||||||
|
Signature string // base64
|
||||||
|
Expected bool
|
||||||
|
}
|
||||||
|
testcases := []testcase{
|
||||||
|
{
|
||||||
|
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
|
||||||
|
Message: "",
|
||||||
|
Signature: "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
|
||||||
|
Message: "",
|
||||||
|
Signature: "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
|
||||||
|
Message: "Hello World",
|
||||||
|
Signature: "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
|
||||||
|
Message: "Hello World",
|
||||||
|
Signature: "AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
|
||||||
|
Message: "",
|
||||||
|
Signature: "INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVA",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
|
||||||
|
Message: "",
|
||||||
|
Signature: "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDLXXXX",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
|
||||||
|
Message: "Hello World",
|
||||||
|
Signature: "BkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDLXXXX",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
|
||||||
|
Message: "",
|
||||||
|
Signature: "AUDVvVp7mCtPZtoORKYcMM+idx9yy5+z4TGeoI/PWEUscd5x0QYJ6IPQ/anBSMWPWSRPqHVrEjOIWhP9FsZSMFdG",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
|
||||||
|
Message: "",
|
||||||
|
Signature: "AUDYeG/k6AL9pNuhgK8aJqxIqBIObX867yc3QgdfS70sWEdUg0Msv0Ps24Pt5aQmcI2wZdwI3Egp5tA5PW+wTOw6",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
|
||||||
|
Message: "Hello World",
|
||||||
|
Signature: "AUCkOlzIYSN6T+QzENjlp61Pa2l4EyDDH8c4pFANOwoh3oGi/iZHscAExUSePhbS94KIMgcg+yNp+LsckO+AfLQQ",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
|
||||||
|
Message: "Hello World",
|
||||||
|
Signature: "AUD5MwxtURP3tAip3fS5vVRwa4L15wEyTIG0BQ3DPktJpXvQe7Sh8kf+mVaO4ldEP+vhiVZ/sXvOHEbQQnsiYpCq",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(fmt.Sprintf("%s_%s", tc.Address, tc.Message), func(t *testing.T) {
|
||||||
|
address, err := btcutils.SafeNewAddress(tc.Address)
|
||||||
|
require.NoError(t, err)
|
||||||
|
signature, err := base64.StdEncoding.DecodeString(tc.Signature)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
verified := VerifyMessage(&address, signature, tc.Message)
|
||||||
|
assert.Equal(t, tc.Expected, verified)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignMessage(t *testing.T) {
|
||||||
|
type testcase struct {
|
||||||
|
PrivateKey *btcec.PrivateKey
|
||||||
|
Address string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
testcases := []testcase{
|
||||||
|
{
|
||||||
|
PrivateKey: lo.Must(btcutil.DecodeWIF("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k")).PrivKey,
|
||||||
|
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
|
||||||
|
Message: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateKey: lo.Must(btcutil.DecodeWIF("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k")).PrivKey,
|
||||||
|
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
|
||||||
|
Message: "Hello World",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateKey: lo.Must(btcutil.DecodeWIF("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k")).PrivKey,
|
||||||
|
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
|
||||||
|
Message: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateKey: lo.Must(btcutil.DecodeWIF("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k")).PrivKey,
|
||||||
|
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
|
||||||
|
Message: "Hello World",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(fmt.Sprintf("%s_%s", tc.Address, tc.Message), func(t *testing.T) {
|
||||||
|
address, err := btcutils.SafeNewAddress(tc.Address)
|
||||||
|
require.NoError(t, err)
|
||||||
|
signature, err := SignMessage(tc.PrivateKey, &address, tc.Message)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
verified := VerifyMessage(&address, signature, tc.Message)
|
||||||
|
assert.True(t, verified)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
77
pkg/bip322/bip322_util.go
Normal file
77
pkg/bip322/bip322_util.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package bip322
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/gaze-network/indexer-network/common/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SerializeWitnessSignature(witness wire.TxWitness) ([]byte, error) {
|
||||||
|
result := new(bytes.Buffer)
|
||||||
|
buf := make([]byte, 8)
|
||||||
|
|
||||||
|
if err := wire.WriteVarIntBuf(result, 0, uint64(len(witness)), buf); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
for _, item := range witness {
|
||||||
|
if err := wire.WriteVarBytesBuf(result, 0, item, buf); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeserializeWitnessSignature(serialized []byte) (wire.TxWitness, error) {
|
||||||
|
if len(serialized) == 0 {
|
||||||
|
return nil, errors.Wrap(errs.ArgumentRequired, "serialized witness is required")
|
||||||
|
}
|
||||||
|
witness := make(wire.TxWitness, 0)
|
||||||
|
|
||||||
|
current := 0
|
||||||
|
witnessLen := int(serialized[current])
|
||||||
|
current++
|
||||||
|
for i := 0; i < witnessLen; i++ {
|
||||||
|
if current >= len(serialized) {
|
||||||
|
return nil, errors.Wrap(errs.InvalidArgument, "invalid serialized witness data: not enough bytes")
|
||||||
|
}
|
||||||
|
witnessItemLen := int(serialized[current])
|
||||||
|
current++
|
||||||
|
if current+witnessItemLen > len(serialized) {
|
||||||
|
return nil, errors.Wrap(errs.InvalidArgument, "invalid serialized witness data: not enough bytes")
|
||||||
|
}
|
||||||
|
witnessItem := serialized[current : current+witnessItemLen]
|
||||||
|
current += witnessItemLen
|
||||||
|
witness = append(witness, witnessItem)
|
||||||
|
}
|
||||||
|
return witness, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayToTaprootScript creates a pk script for a pay-to-taproot output key.
|
||||||
|
func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) {
|
||||||
|
script, err := txscript.NewScriptBuilder().
|
||||||
|
AddOp(txscript.OP_1).
|
||||||
|
AddData(schnorr.SerializePubKey(taprootKey)).
|
||||||
|
Script()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return script, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayToWitnessScript creates a pk script for a pay-to-wpkh output key.
|
||||||
|
func PayToWitnessScript(pubkey *btcec.PublicKey) ([]byte, error) {
|
||||||
|
script, err := txscript.NewScriptBuilder().
|
||||||
|
AddOp(txscript.OP_0).
|
||||||
|
AddData(btcutil.Hash160(pubkey.SerializeCompressed())).
|
||||||
|
Script()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return script, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user