Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Field encapsulation #26

Merged
merged 10 commits into from
Jul 28, 2021
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
test:
strategy:
matrix:
go-version: [ 1.14.x, 1.15.x ]
go-version: [ 1.15.x,1.16.x, ]
os: [ ubuntu-latest ]
runs-on: ${{ matrix.os }}
steps:
Expand Down
4 changes: 1 addition & 3 deletions bscript/addressvalidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ func (a *a25) set58(s []byte) error {
// Checks both mainnet and testnet.
func ValidateAddress(address string) (bool, error) {
if strings.HasPrefix(address, "bitcoin-script:") {
_, _, _, _, err := DecodeBIP276(address)

if err != nil {
if _, err := DecodeBIP276(address); err != nil {
return false, fmt.Errorf("bitcoin-script invalid [%w]", err)
}
return true, nil
Expand Down
57 changes: 34 additions & 23 deletions bscript/bip276.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ import (
"github.com/libsv/go-bk/crypto"
)

// BIP276 proposes a scheme for encoding typed bitcoin related data in a user friendly way
// see https://github.com/moneybutton/bips/blob/master/bip-0276.mediawiki
type BIP276 struct {
Prefix string
Version int
Network int
Data []byte
}

// PrefixScript is the prefix in the BIP276 standard which
// specifies if it is a script or template.
const PrefixScript = "bitcoin-script"
Expand All @@ -35,52 +44,54 @@ var validBIP276 = regexp.MustCompile(`^(.+?):(\d{2})(\d{2})([0-9A-Fa-f]+)([0-9A-

// EncodeBIP276 is used to encode specific (non-standard) scripts in BIP276 format.
// See https://github.com/moneybutton/bips/blob/master/bip-0276.mediawiki
func EncodeBIP276(prefix string, network, version int, data []byte) string {
if version == 0 || version > 255 || network == 0 || network > 255 {
func EncodeBIP276(script BIP276) string {
if script.Version == 0 || script.Version > 255 || script.Network == 0 || script.Network > 255 {
return "ERROR"
}

p, c := createBIP276(prefix, network, version, data)
p, c := createBIP276(script)

return p + c
}

func createBIP276(prefix string, network, version int, data []byte) (string, string) {
payload := fmt.Sprintf("%s:%.2x%.2x%x", prefix, network, version, data)
func createBIP276(script BIP276) (string, string) {
payload := fmt.Sprintf("%s:%.2x%.2x%x", script.Prefix, script.Network, script.Version, script.Data)
return payload, hex.EncodeToString(crypto.Sha256d([]byte(payload))[:4])
}

// DecodeBIP276 is used to decode BIP276 formatted data into specific (non-standard) scripts.
// See https://github.com/moneybutton/bips/blob/master/bip-0276.mediawiki
func DecodeBIP276(text string) (prefix string, version, network int, data []byte, err error) {
func DecodeBIP276(text string) (*BIP276, error) {

// Determine if regex match
res := validBIP276.FindStringSubmatch(text)

// Check if we got a result from the regex match first
if len(res) == 0 {
err = fmt.Errorf("text did not match the BIP276 format")
return
return nil, fmt.Errorf("text did not match the BIP276 format")
}

// Set the prefix
prefix = res[1]

if version, err = strconv.Atoi(res[2]); err != nil {
return
s := BIP276{
Prefix: res[1],
}

if network, err = strconv.Atoi(res[3]); err != nil {
return
version, err := strconv.Atoi(res[2])
if err != nil {
return nil, err
}

if data, err = hex.DecodeString(res[4]); err != nil {
return
s.Version = version
network, err := strconv.Atoi(res[3])
if err != nil {
return nil, err
}

if _, checkSum := createBIP276(prefix, network, version, data); res[5] != checkSum {
err = errors.New("invalid checksum")
s.Network = network
data, err := hex.DecodeString(res[4])
if err != nil {
return nil, err
}
s.Data = data
if _, checkSum := createBIP276(s); res[5] != checkSum {
return nil, errors.New("invalid checksum")
}

return
return &s, nil
}
101 changes: 51 additions & 50 deletions bscript/bip276_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,87 +13,90 @@ func TestEncodeBIP276(t *testing.T) {

t.Run("valid encode (mainnet)", func(t *testing.T) {
s := bscript.EncodeBIP276(
bscript.PrefixScript,
bscript.NetworkMainnet,
bscript.CurrentVersion,
[]byte("fake script"),
bscript.BIP276{
Prefix: bscript.PrefixScript,
Version: bscript.CurrentVersion,
Network: bscript.NetworkMainnet,
Data: []byte("fake script"),
},
)

assert.Equal(t, "bitcoin-script:010166616b65207363726970746f0cd86a", s)
})

t.Run("valid encode (testnet)", func(t *testing.T) {
s := bscript.EncodeBIP276(
bscript.PrefixScript,
bscript.NetworkTestnet,
bscript.CurrentVersion,
[]byte("fake script"),
bscript.BIP276{
Prefix: bscript.PrefixScript,
Version: bscript.CurrentVersion,
Network: bscript.NetworkTestnet,
Data: []byte("fake script"),
},
)

assert.Equal(t, "bitcoin-script:020166616b65207363726970742577a444", s)
})

t.Run("invalid version = 0", func(t *testing.T) {
s := bscript.EncodeBIP276(
bscript.PrefixScript,
bscript.NetworkMainnet,
0,
[]byte("fake script"),
bscript.BIP276{
Prefix: bscript.PrefixScript,
Version: 0,
Network: bscript.NetworkMainnet,
Data: []byte("fake script"),
},
)

assert.Equal(t, "ERROR", s)
})

t.Run("invalid version > 255", func(t *testing.T) {
s := bscript.EncodeBIP276(
bscript.PrefixScript,
bscript.NetworkMainnet,
256,
[]byte("fake script"),
bscript.BIP276{
Prefix: bscript.PrefixScript,
Version: 256,
Network: bscript.NetworkMainnet,
Data: []byte("fake script"),
},
)

assert.Equal(t, "ERROR", s)
})

t.Run("invalid network = 0", func(t *testing.T) {
s := bscript.EncodeBIP276(
bscript.PrefixScript,
0,
bscript.CurrentVersion,
[]byte("fake script"),
)

assert.Equal(t, "ERROR", s)
})

t.Run("invalid version > 255", func(t *testing.T) {
s := bscript.EncodeBIP276(
bscript.PrefixScript,
256,
bscript.CurrentVersion,
[]byte("fake script"),
bscript.BIP276{
Prefix: bscript.PrefixScript,
Version: bscript.CurrentVersion,
Network: 0,
Data: []byte("fake script"),
},
)

assert.Equal(t, "ERROR", s)
})

t.Run("different prefix", func(t *testing.T) {
s := bscript.EncodeBIP276(
"different-prefix",
bscript.NetworkMainnet,
bscript.CurrentVersion,
[]byte("fake script"),
bscript.BIP276{
Prefix: "different-prefix",
Version: bscript.CurrentVersion,
Network: bscript.NetworkMainnet,
Data: []byte("fake script"),
},
)

assert.Equal(t, "different-prefix:010166616b6520736372697074effdb090", s)
})

t.Run("template prefix", func(t *testing.T) {
s := bscript.EncodeBIP276(
bscript.PrefixTemplate,
bscript.NetworkMainnet,
bscript.CurrentVersion,
[]byte("fake script"),
bscript.BIP276{
Prefix: bscript.PrefixTemplate,
Version: bscript.CurrentVersion,
Network: bscript.NetworkMainnet,
Data: []byte("fake script"),
},
)

assert.Equal(t, "bitcoin-template:010166616b65207363726970749e31aa72", s)
Expand All @@ -104,25 +107,23 @@ func TestDecodeBIP276(t *testing.T) {
t.Parallel()

t.Run("valid decode", func(t *testing.T) {
prefix, network, version, data, err := bscript.DecodeBIP276("bitcoin-script:010166616b65207363726970746f0cd86a")
script, err := bscript.DecodeBIP276("bitcoin-script:010166616b65207363726970746f0cd86a")
assert.NoError(t, err)
assert.Equal(t, `"bitcoin-script"`, fmt.Sprintf("%q", prefix))
assert.Equal(t, 1, network)
assert.Equal(t, 1, version)
assert.Equal(t, "fake script", fmt.Sprintf("%s", data))
assert.Equal(t, `"bitcoin-script"`, fmt.Sprintf("%q", script.Prefix))
assert.Equal(t, 1, script.Network)
assert.Equal(t, 1, script.Version)
assert.Equal(t, "fake script", string(script.Data))
})

t.Run("invalid decode", func(t *testing.T) {
_, _, _, _, err := bscript.DecodeBIP276("bitcoin-script:01")
script, err := bscript.DecodeBIP276("bitcoin-script:01")
assert.Error(t, err)
assert.Nil(t, script)
})

t.Run("valid format, bad checksum", func(t *testing.T) {
prefix, network, version, data, err := bscript.DecodeBIP276("bitcoin-script:010166616b65207363726970746f0cd8")
script, err := bscript.DecodeBIP276("bitcoin-script:010166616b65207363726970746f0cd8")
assert.Error(t, err)
assert.Equal(t, `"bitcoin-script"`, fmt.Sprintf("%q", prefix))
assert.Equal(t, 1, network)
assert.Equal(t, 1, version)
assert.Equal(t, "fake scrip", fmt.Sprintf("%s", data))
assert.Nil(t, script)
})
}
15 changes: 6 additions & 9 deletions bscript/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,9 @@ func NewFromBytes(b []byte) *Script {

// NewFromASM creates a new script from a BitCoin ASM formatted string.
func NewFromASM(str string) (*Script, error) {
sections := strings.Split(str, " ")
s := Script{}

s := &Script{}

for _, section := range sections {
for _, section := range strings.Split(str, " ") {
if val, ok := opCodeStrings[section]; ok {
s.AppendOpCode(val)
} else {
Expand All @@ -61,14 +59,13 @@ func NewFromASM(str string) (*Script, error) {
}
}

return s, nil
return &s, nil
}

// NewP2PKHFromPubKeyEC takes a public key hex string (in
// compressed format) and creates a P2PKH script from it.
func NewP2PKHFromPubKeyEC(pubKey *bec.PublicKey) (*Script, error) {
pubKeyBytes := pubKey.SerialiseCompressed()
return NewP2PKHFromPubKeyBytes(pubKeyBytes)
return NewP2PKHFromPubKeyBytes(pubKey.SerialiseCompressed())
}

// NewP2PKHFromPubKeyStr takes a public key hex string (in
Expand Down Expand Up @@ -200,8 +197,8 @@ func (s *Script) AppendOpCode(o uint8) *Script {
return s
}

// ToString returns hex string of script.
func (s *Script) String() string { // TODO: change to HexString?
// String implements the stringer interface and returns the hex string of script.
func (s *Script) String() string {
return hex.EncodeToString(*s)
}

Expand Down
5 changes: 2 additions & 3 deletions bscript/unlockingscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import "github.com/libsv/go-bt/sighash"
// NewP2PKHUnlockingScript creates a new unlocking script which spends
// a P2PKH locking script from a public key, a signature, and
// a SIGHASH flag.
func NewP2PKHUnlockingScript(pubKey []byte, sig []byte,
sigHashFlag sighash.Flag) (*Script, error) {
func NewP2PKHUnlockingScript(pubKey []byte, sig []byte, sigHashFlag sighash.Flag) (*Script, error) {

// append SIGHASH to DER sig
sigBuf := []byte{}
sigBuf := make([]byte, 0)
sigBuf = append(sigBuf, sig...)
sigBuf = append(sigBuf, uint8(sigHashFlag))

Expand Down
4 changes: 2 additions & 2 deletions examples/create_tx/create_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ func main() {
"76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac6a0568656c6c6f",
1500)

_ = tx.PayTo("1NRoySJ9Lvby6DuE2UQYnyT67AASwNZxGb", 1000)
_ = tx.PayToAddress("1NRoySJ9Lvby6DuE2UQYnyT67AASwNZxGb", 1000)

wif, _ := wif.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS")

inputsSigned, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey})
if err != nil && len(inputsSigned) > 0 {
log.Fatal(err.Error())
}
log.Println("tx: ", tx.String())
log.Printf("tx: %s\n", tx)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func main() {
"76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac",
1000)

_ = tx.PayTo("1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", 900)
_ = tx.PayToAddress("1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", 900)

_ = tx.AddOpReturnOutput([]byte("You are using go-bt!"))

Expand Down
Loading