Compare commits

...

3 Commits
v0.7.0 ... main

Author SHA1 Message Date
Gaze
58f8497997 feat: add code to errs.PublicError 2024-11-26 14:39:27 +07:00
Gaze
920f7fe07b chore: go mod tidy 2024-11-22 14:23:53 +07:00
Gaze
0cb66232ef feat: add bip322 pkg 2024-11-22 14:22:07 +07:00
5 changed files with 437 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ import (
type PublicError struct {
err error
message string
code string // code is optional, it can be used to identify the error type
}
func (p PublicError) Error() string {
@@ -21,6 +22,10 @@ func (p PublicError) Message() string {
return p.message
}
func (p PublicError) Code() string {
return p.code
}
func (p PublicError) Unwrap() error {
return p.err
}
@@ -29,6 +34,10 @@ func NewPublicError(message string) error {
return withstack.WithStackDepth(&PublicError{err: errors.New(message), message: message}, 1)
}
func NewPublicErrorWithCode(message string, code string) error {
return withstack.WithStackDepth(&PublicError{err: errors.New(message), message: message, code: code}, 1)
}
func WithPublicMessage(err error, prefix string) error {
if err == nil {
return nil
@@ -41,3 +50,16 @@ func WithPublicMessage(err error, prefix string) error {
}
return withstack.WithStackDepth(&PublicError{err: err, message: message}, 1)
}
func WithPublicMessageCode(err error, prefix string, code string) error {
if err == nil {
return nil
}
var message string
if prefix != "" {
message = fmt.Sprintf("%s: %s", prefix, err.Error())
} else {
message = err.Error()
}
return withstack.WithStackDepth(&PublicError{err: err, message: message, code: code}, 1)
}

2
go.sum
View File

@@ -99,6 +99,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -232,6 +233,7 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=

191
pkg/bip322/bip322.go Normal file
View 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
View 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
View 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
}