Skip to content

Commit

Permalink
Updated to v0.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ArbitronNL committed May 29, 2024
1 parent 83900b0 commit b6a0da9
Show file tree
Hide file tree
Showing 26 changed files with 1,925 additions and 327 deletions.
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ linters:
- gocritic
- gocyclo
- godot
- godox
- gofmt
- gofumpt
- goheader
Expand Down
45 changes: 36 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,51 @@ For examples, checkout the [example](/.example) folder.

## Support

This library tries to support as many signatures as possible.
This library tries to support as many signatures as possible, as long as they properly follow specifications.

### Generic / BIP-0137

This specification is considered [legacy signing in BIP-322](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#legacy).

#### Supported

**Current support:**
- Any wallet that does signing like Electrum, example:
- Electrum: P2PKH, P2WPKH and P2WPKH-P2SH
- Coinomi: P2PKH, P2WPKH and P2WPKH-P2SH
- Samourai: P2PKH, P2WPKH and P2WPKH-P2SH
- Mycelium: P2PKH, P2WPKH and P2WPKH-P2SH
- Electrum: P2PKH, P2WPKH and P2SH-P2WPKH
- Coinomi: P2PKH, P2WPKH and P2SH-P2WPKH
- Samourai: P2PKH, P2WPKH and P2SH-P2WPKH
- Mycelium: P2PKH, P2WPKH and P2SH-P2WPKH
- Any wallet that allows for legacy address signatures (P2PKH), example:
- Bitcoin Core
- Any wallet that follows [BIP 137](https://github.com/bitcoin/bips/blob/master/bip-0137.mediawiki), example:
- Trezor: P2PKH, P2WPKH and P2WPKH-P2SH
- Trezor: P2PKH, P2WPKH and P2SH-P2WPKH
- Taproot (P2TR)
- The verification is using the internal key, so only addresses without a tapscript are allowed.

**Currently not supported:**
#### Not supported

- Pay-to-Witness-Script-Hash (P2WSH)
- BIP-322

### BIP-322

#### Supported

- [Simple singing](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#simple)
- P2WPKH - Native Segwit
- P2TR - Taproot

#### Not supported

- [Simple singing](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#simple) of other types
- [Full signing](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full)
- [Full singing (Proof of Funds)](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full-proof-of-funds)
- Multisig of any kind

### UniSat

The UniSat wallet [used to not follow established standards](https://github.com/BitonicNL/verify-signed-message/issues/3#issuecomment-1597101994) for signing messages when using non-taproot addresses. Specifically, it used to set incorrect recovery flags, resulting in signatures that are seen as invalid by Electrum, Bitcoin Core, Trezor, etc.

This seems to have been resolved in recent versions of Unisat. Not sure if they resolved it or one of their dependencies resolved it, but in our latest tests it worked as expected.
If you run into issues, make sure you are using the latest version and generate new signatures.

## Development

Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/bitonicnl/verify-signed-message

go 1.18
go 1.21

require (
github.com/btcsuite/btcd v0.24.0
Expand All @@ -17,8 +17,8 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect
golang.org/x/sys v0.20.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand All @@ -98,8 +98,8 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
Expand Down
8 changes: 8 additions & 0 deletions internal/bip322/doc.go
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
151 changes: 151 additions & 0 deletions internal/bip322/pbst.go
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
}
32 changes: 32 additions & 0 deletions internal/bip322/pbst_test.go
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())
}
71 changes: 71 additions & 0 deletions internal/bip322/verify.go
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
}
}
Loading

0 comments on commit b6a0da9

Please sign in to comment.