Skip to content

Commit

Permalink
Merge pull request #26 from libsv/field-encapsulation
Browse files Browse the repository at this point in the history
  • Loading branch information
jadwahab authored Jul 28, 2021
2 parents 7358e36 + 25d921a commit 17eb7ba
Show file tree
Hide file tree
Showing 24 changed files with 553 additions and 220 deletions.
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

0 comments on commit 17eb7ba

Please sign in to comment.