mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-01-12 16:53:08 +08:00
Merge branch 'main' into develop
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