-
Notifications
You must be signed in to change notification settings - Fork 14
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
1 parent
83900b0
commit b6a0da9
Showing
26 changed files
with
1,925 additions
and
327 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 |
---|---|---|
|
@@ -60,7 +60,6 @@ linters: | |
- gocritic | ||
- gocyclo | ||
- godot | ||
- godox | ||
- gofmt | ||
- gofumpt | ||
- goheader | ||
|
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
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
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
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,8 @@ | ||
// Package bip322 holds all the relevant tools to actually build and validate signed message via BIP-322. | ||
// | ||
// For more information, refer: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki | ||
// | ||
// These files were heavily inspired by: | ||
// - https://github.com/brc20-devs/brc20-reference-implementation/blob/main/modules/brc20_swap_index/lib/bip322/verify.go | ||
// - https://github.com/babylonchain/babylon/tree/dev/crypto/bip322 | ||
package bip322 |
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,151 @@ | ||
package bip322 | ||
|
||
import ( | ||
"github.com/btcsuite/btcd/btcutil" | ||
"github.com/btcsuite/btcd/chaincfg/chainhash" | ||
"github.com/btcsuite/btcd/txscript" | ||
"github.com/btcsuite/btcd/wire" | ||
|
||
"github.com/bitonicnl/verify-signed-message/internal" | ||
) | ||
|
||
// Constants for the toSpend transaction. | ||
const ( | ||
// toSpendVersion contains the transaction version. | ||
toSpendVersion = 0 | ||
// toSpendLockTime contains the transaction lock time. | ||
toSpendLockTime = 0 | ||
// toSpendInputHash contains the dummy input hash. | ||
toSpendInputHash = "0000000000000000000000000000000000000000000000000000000000000000" | ||
// toSpendInputIndex contains the dummy input index. | ||
toSpendInputIndex = 0xFFFFFFFF | ||
// toSpendInputSeq contains the sequence number for the input. | ||
toSpendInputSeq = 0 | ||
// toSpendOutputValue contains the output value (in satoshis). | ||
toSpendOutputValue = 0 | ||
) | ||
|
||
// Constants for the toSign transaction. | ||
const ( | ||
// toSignVersion contains the transaction version. | ||
toSignVersion = 0 | ||
// toSignLockTime contains the transaction lock time. | ||
toSignLockTime = 0 | ||
// toSignInputSeq contains the sequence number for the input. | ||
toSignInputSeq = 0 | ||
// toSignOutputValue contains the output value (in satoshis). | ||
toSignOutputValue = 0 | ||
) | ||
|
||
// BuildToSpendTx builds a toSpend transaction based on the BIP-322 spec. It requires the message that is signed and the address that produced the signature. | ||
// | ||
// For more details, refer: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full | ||
func BuildToSpendTx(msg []byte, address btcutil.Address) (*wire.MsgTx, error) { | ||
// Create a new transaction | ||
psbt := wire.NewMsgTx(toSpendVersion) | ||
psbt.LockTime = toSpendLockTime | ||
|
||
// Create an outpoint for the input | ||
inputHash, err := chainhash.NewHashFromStr(toSpendInputHash) | ||
if err != nil { | ||
// This error indicates a programming error since the input hash is predefined | ||
panic(err) | ||
} | ||
outPoint := wire.NewOutPoint(inputHash, toSpendInputIndex) | ||
|
||
// Generate the signature script for the input | ||
script, err := toSpendSignatureScript(msg) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Create the input using the outpoint and signature script | ||
input := wire.NewTxIn(outPoint, script, nil) | ||
input.Sequence = toSpendInputSeq | ||
|
||
// Create the output paying to the provided address | ||
pkScript, err := txscript.PayToAddrScript(address) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Create the output using the pay-to-address script | ||
output := wire.NewTxOut(toSpendOutputValue, pkScript) | ||
|
||
// Add the input and output to the transaction | ||
psbt.AddTxIn(input) | ||
psbt.AddTxOut(output) | ||
|
||
return psbt, nil | ||
} | ||
|
||
// BuildToSignTx builds a toSign transaction based on the BIP-322 spec. // It requires the toSpend transaction that it spends. | ||
// | ||
// For more details, refer: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full | ||
func BuildToSignTx(toSpend *wire.MsgTx) *wire.MsgTx { | ||
// Create a new transaction | ||
toSign := wire.NewMsgTx(toSignVersion) | ||
toSign.LockTime = toSignLockTime | ||
|
||
// Specify the input outpoint | ||
// As the input is from the toSpend transaction, the index is 0 | ||
inputHash := toSpend.TxHash() | ||
outPoint := wire.NewOutPoint(&inputHash, 0) | ||
|
||
// Create the input using the out point | ||
input := wire.NewTxIn(outPoint, nil, nil) | ||
input.Sequence = toSignInputSeq | ||
|
||
// Create the output with an unspendable script | ||
output := wire.NewTxOut(toSignOutputValue, buildSignPkScript()) | ||
|
||
// Add the input and output to the transaction | ||
toSign.AddTxIn(input) | ||
toSign.AddTxOut(output) | ||
|
||
return toSign | ||
} | ||
|
||
// toSpendSignatureScript creates the signature script for the input of the toSpend transaction. It follows the BIP-322 specification. | ||
// | ||
// For more details, refer: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full | ||
func toSpendSignatureScript(msg []byte) ([]byte, error) { | ||
// Create a new script builder | ||
builder := txscript.NewScriptBuilder() | ||
|
||
// Add OP_0 to initialize the witness stack | ||
builder.AddOp(txscript.OP_0) | ||
|
||
// Create the magic message as specified in BIP-322 | ||
data := internal.CreateMagicMessageBIP322(msg) | ||
builder.AddData(data[:]) | ||
|
||
// Generate the script | ||
script, err := builder.Script() | ||
if err != nil { | ||
// Since this is based on the incoming message, this could happen | ||
return nil, err | ||
} | ||
|
||
return script, nil | ||
} | ||
|
||
// buildSignPkScript creates the public key script for the output of the toSign transaction. It follows the BIP-322 specification. | ||
// | ||
// For more details, refer: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full | ||
func buildSignPkScript() []byte { | ||
// Create a new script builder | ||
builder := txscript.NewScriptBuilder() | ||
|
||
// Add OP_RETURN opcode to mark the output as unspendable | ||
builder.AddOp(txscript.OP_RETURN) | ||
|
||
// Generate the script | ||
script, err := builder.Script() | ||
if err != nil { | ||
// Since we are constructing the script, this error should not occur in practice | ||
panic(err) | ||
} | ||
|
||
return script | ||
} |
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,32 @@ | ||
package bip322_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/btcsuite/btcd/btcutil" | ||
"github.com/btcsuite/btcd/chaincfg" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/bitonicnl/verify-signed-message/internal/bip322" | ||
) | ||
|
||
// Taken from https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#transaction-hashes | ||
func TestGetToSignTx(t *testing.T) { | ||
t.Parallel() | ||
|
||
testAddr := "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l" | ||
testAddrDecoded, err := btcutil.DecodeAddress(testAddr, &chaincfg.TestNet3Params) | ||
require.NoError(t, err) | ||
|
||
toSpendTx, err := bip322.BuildToSpendTx([]byte{}, testAddrDecoded) | ||
require.NoError(t, err) | ||
require.Equal(t, "c5680aa69bb8d860bf82d4e9cd3504b55dde018de765a91bb566283c545a99a7", toSpendTx.TxHash().String()) | ||
toSignTx := bip322.BuildToSignTx(toSpendTx) | ||
require.Equal(t, "1e9654e951a5ba44c8604c4de6c67fd78a27e81dcadcfe1edf638ba3aaebaed6", toSignTx.TxHash().String()) | ||
|
||
toSpendTx, err = bip322.BuildToSpendTx([]byte("Hello World"), testAddrDecoded) | ||
require.NoError(t, err) | ||
require.Equal(t, "b79d196740ad5217771c1098fc4a4b51e0535c32236c71f1ea4d61a2d603352b", toSpendTx.TxHash().String()) | ||
toSignTx = bip322.BuildToSignTx(toSpendTx) | ||
require.Equal(t, "88737ae86f2077145f93cc4b153ae9a1cb8d56afa511988c149c5c8c9d93bddf", toSignTx.TxHash().String()) | ||
} |
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,71 @@ | ||
package bip322 | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"reflect" | ||
|
||
"github.com/btcsuite/btcd/btcutil" | ||
"github.com/btcsuite/btcd/txscript" | ||
) | ||
|
||
// TODO: Check if we can implement more by referencing https://github.com/ACken2/bip322-js/blob/main/src/Verifier.ts#L23 | ||
// Their implementation supports *btcutil.AddressScriptHash (but no multisig, yet). | ||
func Verify(address btcutil.Address, message string, signatureDecoded []byte) (bool, error) { | ||
// Ensure we support the address | ||
if !IsSupported(address) { | ||
return false, fmt.Errorf("unsupported address type '%s'", reflect.TypeOf(address)) | ||
} | ||
|
||
// Draft corresponding toSpend and toSign transaction using the message and script pubkey | ||
toSpend, err := BuildToSpendTx([]byte(message), address) | ||
if err != nil { | ||
return false, fmt.Errorf("could not build spending transaction: %w", err) | ||
} | ||
|
||
witness, err := SimpleSigToWitness(signatureDecoded) | ||
if err != nil { | ||
return false, fmt.Errorf("error converting signature into witness: %w", err) | ||
} | ||
|
||
toSign := BuildToSignTx(toSpend) | ||
toSign.TxIn[0].Witness = witness | ||
|
||
// Validate toSign transaction | ||
if len(toSign.TxIn) != 1 || len(toSign.TxOut) != 1 { | ||
return false, errors.New("invalid toSign transaction format") | ||
} | ||
|
||
// From the rules here: | ||
// https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#verification-process | ||
// We only need to perform verification of whether toSign spends toSpend properly | ||
// given that the signature is a simple one, and we construct both toSpend and toSign | ||
inputFetcher := txscript.NewCannedPrevOutputFetcher(toSpend.TxOut[0].PkScript, 0) | ||
sigHashes := txscript.NewTxSigHashes(toSign, inputFetcher) | ||
vm, err := txscript.NewEngine(toSpend.TxOut[0].PkScript, toSign, 0, txscript.StandardVerifyFlags, txscript.NewSigCache(0), sigHashes, toSpend.TxOut[0].Value, inputFetcher) | ||
if err != nil { | ||
return false, fmt.Errorf("could not create new engine: %w", err) | ||
} | ||
|
||
// Execute the script | ||
err = vm.Execute() | ||
if err != nil { | ||
return false, fmt.Errorf("script execution failed: %w", err) | ||
} | ||
|
||
// Verification successful | ||
return true, nil | ||
} | ||
|
||
func IsSupported(address btcutil.Address) bool { | ||
switch address.(type) { | ||
// P2WPKH - Native Segwit | ||
case *btcutil.AddressWitnessPubKeyHash: | ||
return true | ||
// P2TR - Taproot | ||
case *btcutil.AddressTaproot: | ||
return true | ||
default: | ||
return false | ||
} | ||
} |
Oops, something went wrong.