diff --git a/.golangci.yml b/.golangci.yml index 636563be..08b90dd0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -246,7 +246,7 @@ linters-settings: # Correct spellings using locale preferences for US or UK. # Default is to use a neutral variety of English. # Setting locale to US will correct the British spelling of 'colour' to 'color'. - locale: US + locale: UK ignore-words: - bsv - bitcoin @@ -347,7 +347,6 @@ linters: - exhaustive - sqlclosecheck - nolintlint - - gci - goconst - lll disable: @@ -360,6 +359,7 @@ linters: - testpackage - nestif - nlreturn + - gci disable-all: false presets: - bugs diff --git a/bscript/address.go b/bscript/address.go index 701395d4..f16853eb 100644 --- a/bscript/address.go +++ b/bscript/address.go @@ -4,9 +4,16 @@ import ( "encoding/hex" "fmt" - "github.com/bitcoinsv/bsvd/bsvec" - "github.com/bitcoinsv/bsvutil/base58" - "github.com/libsv/go-bt/crypto" + "github.com/libsv/go-bk/base58" + "github.com/libsv/go-bk/bec" + "github.com/libsv/go-bk/crypto" +) + +const ( + hashP2PKH = 0x00 + hashTestNetP2PKH = 0x6f + hashP2SH = 0x05 // TODO: remove deprecated p2sh stuff + hashTestNetP2SH = 0xc4 ) // An Address struct contains the address string as well as the hash160 hexstring of the public key. @@ -38,17 +45,16 @@ func addressToPubKeyHashStr(address string) (string, error) { } switch decoded[0] { - case 0x00: // Pubkey hash (P2PKH address) + case hashP2PKH: // Pubkey hash (P2PKH address) return hex.EncodeToString(decoded[1 : len(decoded)-4]), nil - case 0x6f: // Testnet pubkey hash (P2PKH address) + case hashTestNetP2PKH: // Testnet pubkey hash (P2PKH address) return hex.EncodeToString(decoded[1 : len(decoded)-4]), nil - case 0x05: // Script hash (P2SH address) + case hashP2SH: // Script hash (P2SH address) fallthrough - case 0xc4: // Testnet script hash (P2SH address) + case hashTestNetP2SH: // Testnet script hash (P2SH address) fallthrough - default: return "", fmt.Errorf("address %s is not supported", address) } @@ -78,6 +84,7 @@ func NewAddressFromPublicKeyHash(hash []byte, mainnet bool) (*Address, error) { bb[0] = 111 } + // nolint: makezero // this needs init this way bb = append(bb, hash...) return &Address{ @@ -86,11 +93,11 @@ func NewAddressFromPublicKeyHash(hash []byte, mainnet bool) (*Address, error) { }, nil } -// NewAddressFromPublicKey takes a bsvec public key and returns an Address struct pointer. +// NewAddressFromPublicKey takes a bec public key and returns an Address struct pointer. // If mainnet parameter is true it will return a mainnet address (starting with a 1). // Otherwise (mainnet is false) it will return a testnet address (starting with an m or n). -func NewAddressFromPublicKey(pubKey *bsvec.PublicKey, mainnet bool) (*Address, error) { - hash := crypto.Hash160(pubKey.SerializeCompressed()) +func NewAddressFromPublicKey(pubKey *bec.PublicKey, mainnet bool) (*Address, error) { + hash := crypto.Hash160(pubKey.SerialiseCompressed()) // regtest := 111 // mainnet: 0 @@ -99,7 +106,7 @@ func NewAddressFromPublicKey(pubKey *bsvec.PublicKey, mainnet bool) (*Address, e if !mainnet { bb[0] = 111 } - + // nolint: makezero // this needs init this way bb = append(bb, hash...) return &Address{ diff --git a/bscript/address_test.go b/bscript/address_test.go index 964687e0..8d25c0f1 100644 --- a/bscript/address_test.go +++ b/bscript/address_test.go @@ -4,7 +4,7 @@ import ( "encoding/hex" "testing" - "github.com/bitcoinsv/bsvd/bsvec" + "github.com/libsv/go-bk/bec" "github.com/libsv/go-bt/bscript" "github.com/stretchr/testify/assert" ) @@ -88,8 +88,8 @@ func TestNewAddressFromPublicKey(t *testing.T) { pubKeyBytes, err := hex.DecodeString("026cf33373a9f3f6c676b75b543180703df225f7f8edbffedc417718a8ad4e89ce") assert.NoError(t, err) - var pubKey *bsvec.PublicKey - pubKey, err = bsvec.ParsePubKey(pubKeyBytes, bsvec.S256()) + var pubKey *bec.PublicKey + pubKey, err = bec.ParsePubKey(pubKeyBytes, bec.S256()) assert.NoError(t, err) assert.NotNil(t, pubKey) diff --git a/bscript/addressvalidation.go b/bscript/addressvalidation.go index 3056eb00..fe3e085f 100644 --- a/bscript/addressvalidation.go +++ b/bscript/addressvalidation.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/libsv/go-bt/crypto" + "github.com/libsv/go-bk/crypto" ) type a25 [25]byte diff --git a/bscript/bip276.go b/bscript/bip276.go index 10b69aec..9a75726b 100644 --- a/bscript/bip276.go +++ b/bscript/bip276.go @@ -7,7 +7,7 @@ import ( "regexp" "strconv" - "github.com/libsv/go-bt/crypto" + "github.com/libsv/go-bk/crypto" ) // PrefixScript is the prefix in the BIP276 standard which diff --git a/bscript/oppushdata.go b/bscript/oppushdata.go index b32ff6d8..b948f20b 100644 --- a/bscript/oppushdata.go +++ b/bscript/oppushdata.go @@ -90,37 +90,51 @@ func DecodeParts(b []byte) ([][]byte, error) { if len(b) < 2 { return r, errors.New("not enough data") } - l := uint64(b[1]) - if len(b) < int(2+l) { + + l := int(b[1]) + b = b[2:] + + if len(b) < l { return r, errors.New("not enough data") } - part := b[2 : 2+l] + + part := b[:l] r = append(r, part) - b = b[2+l:] + b = b[l:] case OpPUSHDATA2: if len(b) < 3 { return r, errors.New("not enough data") } - l := binary.LittleEndian.Uint16(b[1:]) - if len(b) < int(3+l) { + + l := int(binary.LittleEndian.Uint16(b[1:])) + + b = b[3:] + + if len(b) < l { return r, errors.New("not enough data") } - part := b[3 : 3+l] + + part := b[:l] r = append(r, part) - b = b[3+l:] + b = b[l:] case OpPUSHDATA4: if len(b) < 5 { return r, errors.New("not enough data") } - l := binary.LittleEndian.Uint32(b[1:]) - if len(b) < int(5+l) { + + l := int(binary.LittleEndian.Uint32(b[1:])) + + b = b[5:] + + if len(b) < l { return r, errors.New("not enough data") } - part := b[5 : 5+l] + + part := b[:l] r = append(r, part) - b = b[5+l:] + b = b[l:] default: if b[0] >= 0x01 && b[0] <= OpPUSHDATA4 { diff --git a/bscript/oppushdata_test.go b/bscript/oppushdata_test.go index 7c702428..11255ecc 100644 --- a/bscript/oppushdata_test.go +++ b/bscript/oppushdata_test.go @@ -98,6 +98,31 @@ func TestDecodeParts(t *testing.T) { assert.Equal(t, 0, len(decoded)) }) + t.Run("invalid decode using OP_PUSHDATA2 - overflow", func(t *testing.T) { + + b := make([]byte, 0) + b = append(b, bscript.OpPUSHDATA2) + b = append(b, 0xff) + b = append(b, 0xff) + + bigScript := make([]byte, 0xffff) + + b = append(b, bigScript...) + + t.Logf("Script len is %d", len(b)) + + defer func() { + if r := recover(); r != nil { + t.Errorf("Panic detected: %v", r) + } + }() + + _, err := bscript.DecodeParts(b) + if err != nil { + t.Error(err) + } + }) + t.Run("invalid decode using OP_PUSHDATA4 - payload too small", func(t *testing.T) { data := "testing the code OP_PUSHDATA4" diff --git a/bscript/script.go b/bscript/script.go index a0823fce..04c72dd8 100644 --- a/bscript/script.go +++ b/bscript/script.go @@ -1,13 +1,21 @@ package bscript import ( + "bytes" "encoding/hex" "errors" - "fmt" "strings" - "github.com/bitcoinsv/bsvd/bsvec" - "github.com/libsv/go-bt/crypto" + "github.com/libsv/go-bk/bec" + "github.com/libsv/go-bk/crypto" +) + +// Sentinel errors raised by the package. +var ( + ErrInvalidPKLen = errors.New("invalid public key length") + ErrInvalidOpCode = errors.New("invalid opcode data") + ErrEmptyScript = errors.New("script is empty") + ErrNotP2PKH = errors.New("not a P2PKH") ) // Script type @@ -39,7 +47,7 @@ func NewFromASM(str string) (*Script, error) { s.AppendOpCode(val) } else { if err := s.AppendPushDataHexString(section); err != nil { - return nil, errors.New("invalid opcode data") + return nil, ErrInvalidOpCode } } } @@ -49,10 +57,8 @@ func NewFromASM(str string) (*Script, error) { // NewP2PKHFromPubKeyEC takes a public key hex string (in // compressed format) and creates a P2PKH script from it. -func NewP2PKHFromPubKeyEC(pubKey *bsvec.PublicKey) (*Script, error) { - - pubKeyBytes := pubKey.SerializeCompressed() - +func NewP2PKHFromPubKeyEC(pubKey *bec.PublicKey) (*Script, error) { + pubKeyBytes := pubKey.SerialiseCompressed() return NewP2PKHFromPubKeyBytes(pubKeyBytes) } @@ -63,18 +69,15 @@ func NewP2PKHFromPubKeyStr(pubKey string) (*Script, error) { if err != nil { return nil, err } - return NewP2PKHFromPubKeyBytes(pubKeyBytes) } // NewP2PKHFromPubKeyBytes takes public key bytes (in // compressed format) and creates a P2PKH script from it. func NewP2PKHFromPubKeyBytes(pubKeyBytes []byte) (*Script, error) { - if len(pubKeyBytes) != 33 { - return nil, errors.New("invalid public key length") + return nil, ErrInvalidPKLen } - return NewP2PKHFromPubKeyHash(crypto.Hash160(pubKeyBytes)) } @@ -108,7 +111,6 @@ func NewP2PKHFromPubKeyHashStr(pubKeyHash string) (*Script, error) { // NewP2PKHFromAddress takes an address // and creates a P2PKH script from it. func NewP2PKHFromAddress(addr string) (*Script, error) { - a, err := NewAddressFromString(addr) if err != nil { return nil, err @@ -119,14 +121,14 @@ func NewP2PKHFromAddress(addr string) (*Script, error) { return nil, err } - s := &Script{} - s.AppendOpCode(OpDUP) - s.AppendOpCode(OpHASH160) + s := new(Script). + AppendOpCode(OpDUP). + AppendOpCode(OpHASH160) if err = s.AppendPushData(publicKeyHashBytes); err != nil { return nil, err } - s.AppendOpCode(OpEQUALVERIFY) - s.AppendOpCode(OpCHECKSIG) + s.AppendOpCode(OpEQUALVERIFY). + AppendOpCode(OpCHECKSIG) return s, nil } @@ -180,17 +182,17 @@ func (s *Script) AppendPushDataStrings(strs []string) error { strBytes := []byte(str) dataBytes = append(dataBytes, strBytes) } - return s.AppendPushDataArray(dataBytes) } // AppendOpCode appends an opcode type to the script -func (s *Script) AppendOpCode(o uint8) { +func (s *Script) AppendOpCode(o uint8) *Script { *s = append(*s, o) + return s } // ToString returns hex string of script. -func (s *Script) ToString() string { // TODO: change to HexString? +func (s *Script) String() string { // TODO: change to HexString? return hex.EncodeToString(*s) } @@ -233,10 +235,7 @@ func (s *Script) IsP2PK() bool { return false } - if len(parts) == 2 && - len(parts[0]) > 0 && - parts[1][0] == OpCHECKSIG { - + if len(parts) == 2 && len(parts[0]) > 0 && parts[1][0] == OpCHECKSIG { pubkey := parts[0] version := pubkey[0] @@ -269,8 +268,8 @@ func (s *Script) IsData() bool { b[0] == 0x00 && b[1] == 0x6a } -// IsMultisigOut returns true if this is a multisig output script. -func (s *Script) IsMultisigOut() bool { +// IsMultiSigOut returns true if this is a multisig output script. +func (s *Script) IsMultiSigOut() bool { parts, err := DecodeParts(*s) if err != nil { return false @@ -301,11 +300,11 @@ func isSmallIntOp(opcode byte) bool { // PublicKeyHash returns a public key hash byte array if the script is a P2PKH script. func (s *Script) PublicKeyHash() ([]byte, error) { if s == nil || len(*s) == 0 { - return nil, fmt.Errorf("script is empty") + return nil, ErrEmptyScript } if (*s)[0] != 0x76 || (*s)[1] != 0xa9 { - return nil, fmt.Errorf("not a P2PKH") + return nil, ErrNotP2PKH } parts, err := DecodeParts((*s)[2:]) @@ -315,3 +314,20 @@ func (s *Script) PublicKeyHash() ([]byte, error) { return parts[0], nil } + +// Equals will compare the script to b and return true if they match. +func (s *Script) Equals(b *Script) bool { + return bytes.Equal(*s, *b) +} + +// EqualsBytes will compare the script to a byte representation of a +// script, b, and return true if they match. +func (s *Script) EqualsBytes(b []byte) bool { + return bytes.Equal(*s, b) +} + +// EqualsHex will compare the script to a hex string h, +// if they match then true is returned otherwise false. +func (s *Script) EqualsHex(h string) bool { + return s.String() == h +} diff --git a/bscript/script_test.go b/bscript/script_test.go index f5a2de1d..aecd5707 100644 --- a/bscript/script_test.go +++ b/bscript/script_test.go @@ -5,9 +5,10 @@ import ( "strings" "testing" - "github.com/bitcoinsv/bsvd/bsvec" - "github.com/libsv/go-bt/bscript" + "github.com/libsv/go-bk/bec" "github.com/stretchr/testify/assert" + + "github.com/libsv/go-bt/bscript" ) func TestNewP2PKHFromPubKeyStr(t *testing.T) { @@ -29,7 +30,7 @@ func TestNewP2PKHFromPubKey(t *testing.T) { pk, _ := hex.DecodeString("023717efaec6761e457f55c8417815505b695209d0bbfed8c3265be425b373c2d6") - pubkey, err := bsvec.ParsePubKey(pk, bsvec.S256()) + pubkey, err := bec.ParsePubKey(pk, bec.S256()) assert.NoError(t, err) scriptP2PKH, err := bscript.NewP2PKHFromPubKeyEC(pubkey) @@ -133,7 +134,7 @@ func TestScript_IsMultisigOut(t *testing.T) { // TODO: check this scriptPub := bscript.NewFromBytes(b) assert.NotNil(t, scriptPub) - assert.Equal(t, true, scriptPub.IsMultisigOut()) + assert.Equal(t, true, scriptPub.IsMultiSigOut()) } func TestScript_PublicKeyHash(t *testing.T) { @@ -180,3 +181,75 @@ func TestErrorIsAppended(t *testing.T) { assert.NoError(t, err) assert.True(t, strings.HasSuffix(asm, "[error]"), "toASM() should end with [error]") } + +func TestScript_Equals(t *testing.T) { + t.Parallel() + tests := map[string]struct { + script1 *bscript.Script + script2 *bscript.Script + exp bool + }{ + "P2PKH scripts that equal should return true": { + script1: func() *bscript.Script { + s, err := bscript.NewP2PKHFromAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk") + assert.NoError(t, err) + return s + }(), + script2: func() *bscript.Script { + s, err := bscript.NewP2PKHFromAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk") + assert.NoError(t, err) + return s + }(), + exp: true, + }, "scripts from bytes equal should return true": { + script1: func() *bscript.Script { + b, err := hex.DecodeString("5201110122013353ae") + assert.NoError(t, err) + + return bscript.NewFromBytes(b) + }(), + script2: func() *bscript.Script { + b, err := hex.DecodeString("5201110122013353ae") + assert.NoError(t, err) + + return bscript.NewFromBytes(b) + }(), + exp: true, + }, "scripts from hex, equal should return true": { + script1: func() *bscript.Script { + s, err := bscript.NewFromHexString("76a91404d03f746652cfcb6cb55119ab473a045137d26588ac") + assert.NoError(t, err) + assert.NotNil(t, s) + return s + }(), + script2: func() *bscript.Script { + s, err := bscript.NewFromHexString("76a91404d03f746652cfcb6cb55119ab473a045137d26588ac") + assert.NoError(t, err) + assert.NotNil(t, s) + return s + }(), + exp: true, + }, "scripts from hex, not equal should return false": { + script1: func() *bscript.Script { + s, err := bscript.NewFromHexString("76a91404d03f746652cfcb6cb55119ab473a045137d26566ac") + assert.NoError(t, err) + assert.NotNil(t, s) + return s + }(), + script2: func() *bscript.Script { + s, err := bscript.NewFromHexString("76a91404d03f746652cfcb6cb55119ab473a045137d26588ac") + assert.NoError(t, err) + assert.NotNil(t, s) + return s + }(), + exp: false, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, test.exp, test.script1.Equals(test.script2)) + assert.Equal(t, test.exp, test.script1.EqualsBytes(*test.script2)) + assert.Equal(t, test.exp, test.script1.EqualsHex(test.script2.String())) + }) + } +} diff --git a/bscript/unlockingscript_test.go b/bscript/unlockingscript_test.go index 152d4886..a1672a32 100644 --- a/bscript/unlockingscript_test.go +++ b/bscript/unlockingscript_test.go @@ -3,7 +3,7 @@ package bscript import ( "testing" - "github.com/bitcoinsv/bsvutil" + "github.com/libsv/go-bk/wif" "github.com/stretchr/testify/assert" ) @@ -11,15 +11,15 @@ func TestNewP2PKHUnlockingScript(t *testing.T) { t.Run("unlock script with valid pubkey", func(t *testing.T) { - wif, err := bsvutil.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") + wif, err := wif.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") assert.NoError(t, err) assert.NotNil(t, wif) var script *Script - script, err = NewP2PKHUnlockingScript(wif.SerializePubKey(), []byte("some-signature"), 0) + script, err = NewP2PKHUnlockingScript(wif.SerialisePubKey(), []byte("some-signature"), 0) assert.NoError(t, err) assert.NotNil(t, script) - assert.Equal(t, "0f736f6d652d7369676e6174757265002102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66", script.ToString()) + assert.Equal(t, "0f736f6d652d7369676e6174757265002102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66", script.String()) }) } diff --git a/bytemanipulation_test.go b/bytemanipulation_test.go index dd39bef6..6af52155 100644 --- a/bytemanipulation_test.go +++ b/bytemanipulation_test.go @@ -4,8 +4,8 @@ import ( "encoding/hex" "testing" + "github.com/libsv/go-bk/crypto" "github.com/libsv/go-bt" - "github.com/libsv/go-bt/crypto" "github.com/stretchr/testify/assert" ) diff --git a/crypto/encryption.go b/crypto/encryption.go deleted file mode 100644 index ee8b4f30..00000000 --- a/crypto/encryption.go +++ /dev/null @@ -1,37 +0,0 @@ -package crypto - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "errors" - "io" -) - -// Encrypt is an encrypt function -// todo: write test and documentation -func Encrypt(cipherBlock cipher.Block, text []byte) ([]byte, error) { - b := base64.StdEncoding.EncodeToString(text) - ciphertext := make([]byte, aes.BlockSize+len(b)) - iv := ciphertext[:aes.BlockSize] - if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return nil, err - } - cfb := cipher.NewCFBEncrypter(cipherBlock, iv) - cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b)) - return ciphertext, nil -} - -// Decrypt is a decrypt function -// todo: write test and documentation -func Decrypt(cipherBlock cipher.Block, ciphertext []byte) ([]byte, error) { - if len(ciphertext) < aes.BlockSize { - return nil, errors.New("ciphertext too short") - } - iv := ciphertext[:aes.BlockSize] - cfb := cipher.NewCFBDecrypter(cipherBlock, iv) - text := ciphertext[aes.BlockSize:] - cfb.XORKeyStream(text, text) - return base64.StdEncoding.DecodeString(string(text)) -} diff --git a/crypto/encryption_test.go b/crypto/encryption_test.go deleted file mode 100644 index d82cb0be..00000000 --- a/crypto/encryption_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package crypto - -import ( - "crypto/aes" - "crypto/cipher" - "encoding/hex" - "testing" - - "github.com/stretchr/testify/assert" -) - -const testKey = "2b7e151628aed2a6abf7158809cf4f3c" - -func TestEncrypt(t *testing.T) { - t.Parallel() - - t.Run("valid aes encryption", func(t *testing.T) { - key, err := hex.DecodeString(testKey) - assert.NoError(t, err) - - testStr := "7468697320697320612074657374" - var testData []byte - testData, err = hex.DecodeString(testStr) - assert.NoError(t, err) - - var block cipher.Block - block, err = aes.NewCipher(key) - assert.NoError(t, err) - assert.NotNil(t, block) - - var encrypted []byte - encrypted, err = Encrypt(block, testData) - assert.NoError(t, err) - // t.Logf("%x", encrypted) - - var decrypted []byte - decrypted, err = Decrypt(block, encrypted) - assert.NoError(t, err) - assert.Equal(t, "this is a test", string(decrypted)) - assert.Equal(t, "7468697320697320612074657374", hex.EncodeToString(decrypted)) - }) -} - -func TestDecrypt(t *testing.T) { - t.Parallel() - - t.Run("valid aes decryption", func(t *testing.T) { - key, err := hex.DecodeString(testKey) - assert.NoError(t, err) - - encryptedString := "16c9de9d806edf8bf7512f1654f0d72c63e4698d61714d1e7c394ada99ef10d8e43c0b22" - var encryptedData []byte - encryptedData, err = hex.DecodeString(encryptedString) - assert.NoError(t, err) - - var block cipher.Block - block, err = aes.NewCipher(key) - assert.NoError(t, err) - assert.NotNil(t, block) - - var decrypted []byte - decrypted, err = Decrypt(block, encryptedData) - assert.NoError(t, err) - assert.Equal(t, "this is a test", string(decrypted)) - assert.Equal(t, "7468697320697320612074657374", hex.EncodeToString(decrypted)) - }) - - t.Run("invalid cipher text", func(t *testing.T) { - - key, err := hex.DecodeString(testKey) - assert.NoError(t, err) - - encryptedString := "000000" - var encryptedData []byte - encryptedData, err = hex.DecodeString(encryptedString) - assert.NoError(t, err) - - var block cipher.Block - block, err = aes.NewCipher(key) - assert.NoError(t, err) - assert.NotNil(t, block) - - _, err = Decrypt(block, encryptedData) - assert.Error(t, err) - }) -} diff --git a/crypto/hash.go b/crypto/hash.go deleted file mode 100644 index d27c5010..00000000 --- a/crypto/hash.go +++ /dev/null @@ -1,32 +0,0 @@ -package crypto - -import ( - "crypto/sha256" - - "golang.org/x/crypto/ripemd160" -) - -// Sha256 calculates hash(b) and returns the resulting bytes. -func Sha256(b []byte) []byte { - data := sha256.Sum256(b) - return data[:] -} - -// Sha256d calculates hash(hash(b)) and returns the resulting bytes. -func Sha256d(b []byte) []byte { - first := Sha256(b) - return Sha256(first[:]) -} - -// Ripemd160 hashes with RIPEMD160 -func Ripemd160(b []byte) []byte { - ripe := ripemd160.New() - _, _ = ripe.Write(b[:]) - return ripe.Sum(nil) -} - -// Hash160 hashes with SHA256 and then hashes again with RIPEMD160. -func Hash160(b []byte) []byte { - hash := Sha256(b) - return Ripemd160(hash[:]) -} diff --git a/crypto/hash_test.go b/crypto/hash_test.go deleted file mode 100644 index aaaeb421..00000000 --- a/crypto/hash_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package crypto - -import ( - "bytes" - "encoding/hex" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestHashFunctions(t *testing.T) { - t.Parallel() - - var hashTests = []struct { - testName string - input string - expected string - hashFunc func([]byte) []byte - }{ - { - "Test Ripemd160 Empty String", - "", - "9c1185a5c5e9fc54612808977ee8f548b2258d31", - Ripemd160, - }, - { - "Test Ripemd160 String", - "I am a test", - "09a23f506b4a37cabab8a9e49b541de582fca96b", - Ripemd160, - }, - { - "Test Sha256d Empty String", - "", - "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456", - Sha256d, - }, - { - "Test Sha256 d String", - "this is the data I want to hash", - "2209ddda5914a3fbad507ff2284c4b6e559c18a669f9fc3ad3b5826a2a999d58", - Sha256d, - }, - { - "Test Sha256 Empty String", - "", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - Sha256, - }, - { - "Test Sha256 String", - "this is the data I want to hash", - "f88eec7ecabf88f9a64c4100cac1e0c0c4581100492137d1b656ea626cad63e3", - Sha256, - }, - { - "Test Hash160 Empty String", - "", - "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", - Hash160, - }, - { - "Test Hash160 String", - "this is the data I want to hash", - "e7fb13ef86fef4203f042fbfc2703fa628301e90", - Hash160, - }, - } - - for _, hashTest := range hashTests { - t.Run(hashTest.testName, func(t *testing.T) { - - // Decode input string to byte - expectedBytes, err := hex.DecodeString(hashTest.expected) - assert.NoError(t, err) - - // Test the expected bytes - hashResult := hashTest.hashFunc([]byte(hashTest.input)) - assert.Equal(t, true, bytes.Equal(hashResult, expectedBytes)) - }) - } -} diff --git a/examples/create_tx/create_tx.go b/examples/create_tx/create_tx.go index d88c0128..a7df293a 100644 --- a/examples/create_tx/create_tx.go +++ b/examples/create_tx/create_tx.go @@ -1,9 +1,10 @@ package main import ( + "context" "log" - "github.com/bitcoinsv/bsvutil" + "github.com/libsv/go-bk/wif" "github.com/libsv/go-bt" ) @@ -18,11 +19,11 @@ func main() { _ = tx.PayTo("1NRoySJ9Lvby6DuE2UQYnyT67AASwNZxGb", 1000) - wif, _ := bsvutil.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") + wif, _ := wif.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") - inputsSigned, err := tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + 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.ToString()) + log.Println("tx: ", tx.String()) } diff --git a/examples/create_tx_with_opreturn/create_tx_with_opreturn.go b/examples/create_tx_with_opreturn/create_tx_with_opreturn.go index 521f490c..14f33bce 100644 --- a/examples/create_tx_with_opreturn/create_tx_with_opreturn.go +++ b/examples/create_tx_with_opreturn/create_tx_with_opreturn.go @@ -1,9 +1,10 @@ package main import ( + "context" "log" - "github.com/bitcoinsv/bsvutil" + "github.com/libsv/go-bk/wif" "github.com/libsv/go-bt" ) @@ -20,11 +21,11 @@ func main() { _ = tx.AddOpReturnOutput([]byte("You are using go-bt!")) - wif, _ := bsvutil.DecodeWIF("L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu") + wif, _ := wif.DecodeWIF("L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu") - inputsSigned, err := tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + 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.ToString()) + log.Println("tx: ", tx.String()) } diff --git a/go.mod b/go.mod index 9368c2d6..74d1684a 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,6 @@ module github.com/libsv/go-bt go 1.15 require ( - github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173 - github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9 + github.com/libsv/go-bk v0.0.0-20210430094342-ff08e691962b github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 ) diff --git a/go.sum b/go.sum index 957a4d9f..07ffac55 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,22 @@ -github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173 h1:2yTIV9u7H0BhRDGXH5xrAwAz7XibWJtX2dNezMeNsUo= github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173/go.mod h1:BZ1UcC9+tmcDEcdVXgpt13hMczwJxWzpAn68wNs7zRA= -github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9 h1:hFI8rT84FCA0FFy3cFrkW5Nz4FyNKlIdCvEvvTNySKg= github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9/go.mod h1:p44KuNKUH5BC8uX4ONEODaHUR4+ibC8todEAOGQEJAM= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/libsv/go-bk v0.0.0-20210430094342-ff08e691962b h1:NJkme7OR8Mz8emNPiG0Ix2gSBqZyQRasE1LVCp5SvhE= +github.com/libsv/go-bk v0.0.0-20210430094342-ff08e691962b/go.mod h1:xbDkeFFpP0uyFaPLnP6TwaLpAsHaslZ0LftTdWlB6HI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604= -golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/input.go b/input.go index d749fc3a..899ca6e0 100644 --- a/input.go +++ b/input.go @@ -24,7 +24,7 @@ const DefaultSequenceNumber uint32 = 0xFFFFFFFF // Input is a representation of a transaction input // -// DO NOT CHANGE ORDER - Optimized for memory via maligned +// DO NOT CHANGE ORDER - Optimised for memory via maligned // type Input struct { PreviousTxIDBytes []byte @@ -45,7 +45,7 @@ func (i *Input) String() string { `prevTxHash: %s prevOutIndex: %d scriptLen: %d -script: %x +script: %s sequence: %x `, hex.EncodeToString(i.PreviousTxIDBytes), diff --git a/input_test.go b/input_test.go index 10ef1df4..ff8b97c5 100644 --- a/input_test.go +++ b/input_test.go @@ -63,7 +63,7 @@ func TestInput_String(t *testing.T) { assert.Equal(t, 148, s) assert.Equal(t, - "prevTxHash: 6fc75f30a085f3313265b92c818082f9768c13b8a1a107b484023ecf63c86e4c\nprevOutIndex: 1\nscriptLen: 107\nscript: &483045022100f01c1a1679c9437398d691c8497f278fa2d615efc05115688bf2c3335b45c88602201b54437e54fb53bc50545de44ea8c64e9e583952771fcc663c8687dc2638f7854121037e87bbd3b680748a74372640628a8f32d3a841ceeef6f75626ab030c1a04824f\nsequence: ffffffff\n", + "prevTxHash: 6fc75f30a085f3313265b92c818082f9768c13b8a1a107b484023ecf63c86e4c\nprevOutIndex: 1\nscriptLen: 107\nscript: 483045022100f01c1a1679c9437398d691c8497f278fa2d615efc05115688bf2c3335b45c88602201b54437e54fb53bc50545de44ea8c64e9e583952771fcc663c8687dc2638f7854121037e87bbd3b680748a74372640628a8f32d3a841ceeef6f75626ab030c1a04824f\nsequence: ffffffff\n", i.String(), ) }) diff --git a/localsigner.go b/localsigner.go index caaf8306..4e737bd5 100644 --- a/localsigner.go +++ b/localsigner.go @@ -1,19 +1,21 @@ package bt import ( - "github.com/bitcoinsv/bsvd/bsvec" + "context" + + "github.com/libsv/go-bk/bec" "github.com/libsv/go-bt/sighash" ) // LocalSigner implements the Signer interface. It is used to sign a Tx locally -// using a bsvec PrivateKey. +// using a bec PrivateKey. type LocalSigner struct { - PrivateKey *bsvec.PrivateKey + PrivateKey *bec.PrivateKey } // Sign a transaction at a given input index using the PrivateKey passed in through the // InternalSigner struct. -func (is *LocalSigner) Sign(unsignedTx *Tx, index uint32, +func (is *LocalSigner) Sign(ctx context.Context, unsignedTx *Tx, index uint32, shf sighash.Flag) (publicKey []byte, signature []byte, err error) { if shf == 0 { @@ -25,24 +27,24 @@ func (is *LocalSigner) Sign(unsignedTx *Tx, index uint32, return } - return is.SignHash(sh) + return is.SignHash(ctx, sh) } // SignHash a transaction at a given a hash digest using the PrivateKey passed in through the // InternalSigner struct. -func (is *LocalSigner) SignHash(hash []byte) (publicKey []byte, signature []byte, err error) { +func (is *LocalSigner) SignHash(ctx context.Context, hash []byte) (publicKey []byte, signature []byte, err error) { sig, err := is.PrivateKey.Sign(hash) if err != nil { return } - publicKey = is.PrivateKey.PubKey().SerializeCompressed() - signature = sig.Serialize() + publicKey = is.PrivateKey.PubKey().SerialiseCompressed() + signature = sig.Serialise() return } // PublicKey returns the public key which will be used to sign. -func (is *LocalSigner) PublicKey() (publicKey []byte) { - return is.PrivateKey.PubKey().SerializeCompressed() +func (is *LocalSigner) PublicKey(ctx context.Context) (publicKey []byte) { + return is.PrivateKey.PubKey().SerialiseCompressed() } diff --git a/localsigner_test.go b/localsigner_test.go index f06f0957..9a055b69 100644 --- a/localsigner_test.go +++ b/localsigner_test.go @@ -1,12 +1,13 @@ package bt_test import ( + "context" "encoding/hex" "testing" - "github.com/bitcoinsv/bsvutil" "github.com/libsv/go-bt" "github.com/libsv/go-bt/bscript" + . "github.com/libsv/go-bk/wif" "github.com/stretchr/testify/assert" ) @@ -24,12 +25,12 @@ func TestInternalSigner_SignAuto(t *testing.T) { assert.NoError(t, err) // Our private key - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("cNGwGSc7KRrTmdLUZ54fiSXWbhLNDc2Eg5zNucgQxyQCzuQ5YRDq") + var wif *WIF + wif, err = DecodeWIF("cNGwGSc7KRrTmdLUZ54fiSXWbhLNDc2Eg5zNucgQxyQCzuQ5YRDq") assert.NoError(t, err) signer := bt.LocalSigner{PrivateKey: wif.PrivKey} - _, err = tx.SignAuto(&signer) + _, err = tx.SignAuto(context.Background(),&signer) assert.NoError(t, err) expectedSignedTx := "010000000193a35408b6068499e0d5abd799d3e827d9bfe70c9b75ebe209c91d2507232651000000006b483045022100c1d77036dc6cd1f3fa1214b0688391ab7f7a16cd31ea4e5a1f7a415ef167df820220751aced6d24649fa235132f1e6969e163b9400f80043a72879237dab4a1190ad412103b8b40a84123121d260f5c109bc5a46ec819c2e4002e5ba08638783bfb4e01435ffffffff02404b4c00000000001976a91404ff367be719efa79d76e4416ffb072cd53b208888acde94a905000000001976a91404d03f746652cfcb6cb55119ab473a045137d26588ac00000000" @@ -59,12 +60,12 @@ func TestInternalSigner_SignAuto(t *testing.T) { // sigBytes := []byte(*sigScript)[1 : len(*sigScript)-35] // sigHashType, _ := binary.Uvarint([]byte(*sigScript)[len(*sigScript)-35 : len(*sigScript)-34]) // -// publicKey, err := bsvec.ParsePubKey(publicKeyBytes, bsvec.S256()) +// publicKey, err := bec.ParsePubKey(publicKeyBytes, bec.S256()) // if err != nil { // t.Error(err) // return // } -// sig, err := bsvec.ParseDERSignature(sigBytes, bsvec.S256()) +// sig, err := bec.ParseDERSignature(sigBytes, bec.S256()) // if err != nil { // t.Error(err) // return @@ -104,12 +105,12 @@ func TestInternalSigner_SignAuto(t *testing.T) { // sigBytes := []byte(*sigScript)[1 : len(*sigScript)-35] // sigHashType, _ := binary.Uvarint([]byte(*sigScript)[len(*sigScript)-35 : len(*sigScript)-34]) // -// publicKey, err := bsvec.ParsePubKey(publicKeyBytes, bsvec.S256()) +// publicKey, err := bec.ParsePubKey(publicKeyBytes, bec.S256()) // if err != nil { // t.Error(err) // return // } -// sig, err := bsvec.ParseDERSignature(sigBytes, bsvec.S256()) +// sig, err := bec.ParseDERSignature(sigBytes, bec.S256()) // if err != nil { // t.Error(err) // return @@ -144,9 +145,9 @@ func TestInternalSigner_SignAuto(t *testing.T) { // // txid := tx.GetTxID() // // fmt.Println(txid) // -// var sigs = make([]*bsvec.Signature, 2) +// var sigs = make([]*bec.Signature, 2) // var sigHashTypes = make([]uint32, 2) -// var publicKeys = make([]*bsvec.PublicKey, 3) +// var publicKeys = make([]*bec.PublicKey, 3) // // sigScript := tx.Inputs[0].UnlockingScript // @@ -159,28 +160,28 @@ func TestInternalSigner_SignAuto(t *testing.T) { // pk1, _ := hex.DecodeString("039281958c651c013f5b3b007c78be231eeb37f130b925ceff63dc3ac8886f22a3") // pk2, _ := hex.DecodeString("03ac76121ffc9db556b0ce1da978021bd6cb4a5f9553c14f785e15f0e202139e3e") // -// publicKeys[0], err = bsvec.ParsePubKey(pk0, bsvec.S256()) +// publicKeys[0], err = bec.ParsePubKey(pk0, bec.S256()) // if err != nil { // t.Error(err) // return // } -// publicKeys[1], err = bsvec.ParsePubKey(pk1, bsvec.S256()) +// publicKeys[1], err = bec.ParsePubKey(pk1, bec.S256()) // if err != nil { // t.Error(err) // return // } -// publicKeys[2], err = bsvec.ParsePubKey(pk2, bsvec.S256()) +// publicKeys[2], err = bec.ParsePubKey(pk2, bec.S256()) // if err != nil { // t.Error(err) // return // } // -// sigs[0], err = bsvec.ParseDERSignature(sig0Bytes, bsvec.S256()) +// sigs[0], err = bec.ParseDERSignature(sig0Bytes, bec.S256()) // if err != nil { // t.Error(err) // return // } -// sigs[1], err = bsvec.ParseDERSignature(sig1Bytes, bsvec.S256()) +// sigs[1], err = bec.ParseDERSignature(sig1Bytes, bec.S256()) // if err != nil { // t.Error(err) // return @@ -220,9 +221,9 @@ func TestInternalSigner_SignAuto(t *testing.T) { // // txid := tx.GetTxID() // // fmt.Println(txid) // -// var sigs = make([]*bsvec.Signature, 2) +// var sigs = make([]*bec.Signature, 2) // var sigHashTypes = make([]uint32, 2) -// var publicKeys = make([]*bsvec.PublicKey, 3) +// var publicKeys = make([]*bec.PublicKey, 3) // // sigScript := tx.Inputs[0].UnlockingScript // @@ -235,28 +236,28 @@ func TestInternalSigner_SignAuto(t *testing.T) { // pk1, _ := hex.DecodeString("0308b00cf7dfbb64604475e8b18e8450ac6ec04655cfa5c6d4d8a0f3f141ee4194") // pk2, _ := hex.DecodeString("030c7f9342ff6583599db8ee8b52383cadb4cf6fee3650c1ad8f66158a4ff0ebd9") // -// publicKeys[0], err = bsvec.ParsePubKey(pk0, bsvec.S256()) +// publicKeys[0], err = bec.ParsePubKey(pk0, bec.S256()) // if err != nil { // t.Error(err) // return // } -// publicKeys[1], err = bsvec.ParsePubKey(pk1, bsvec.S256()) +// publicKeys[1], err = bec.ParsePubKey(pk1, bec.S256()) // if err != nil { // t.Error(err) // return // } -// publicKeys[2], err = bsvec.ParsePubKey(pk2, bsvec.S256()) +// publicKeys[2], err = bec.ParsePubKey(pk2, bec.S256()) // if err != nil { // t.Error(err) // return // } // -// sigs[0], err = bsvec.ParseDERSignature(sig0Bytes, bsvec.S256()) +// sigs[0], err = bec.ParseDERSignature(sig0Bytes, bec.S256()) // if err != nil { // t.Error(err) // return // } -// sigs[1], err = bsvec.ParseDERSignature(sig1Bytes, bsvec.S256()) +// sigs[1], err = bec.ParseDERSignature(sig1Bytes, bec.S256()) // if err != nil { // t.Error(err) // return diff --git a/output.go b/output.go index 912fb6a6..4a2af50d 100644 --- a/output.go +++ b/output.go @@ -35,7 +35,7 @@ func (o *Output) LockingScriptHexString() string { func (o *Output) String() string { return fmt.Sprintf(`value: %d scriptLen: %d -script: %x +script: %s `, o.Satoshis, len(*o.LockingScript), o.LockingScript) } @@ -52,7 +52,7 @@ func (o *Output) ToBytes() []byte { return h } -// BytesForSigHash returns the proper serialization +// BytesForSigHash returns the proper serialisation // of an output to be hashed and signed (sighash). func (o *Output) BytesForSigHash() []byte { buf := make([]byte, 0) diff --git a/output_test.go b/output_test.go index 727c1c52..e5056920 100644 --- a/output_test.go +++ b/output_test.go @@ -4,8 +4,9 @@ import ( "encoding/hex" "testing" - "github.com/libsv/go-bt" "github.com/stretchr/testify/assert" + + "github.com/libsv/go-bt" ) const outputHexStr = "8a08ac4a000000001976a9148bf10d323ac757268eb715e613cb8e8e1d1793aa88ac00000000" @@ -55,6 +56,6 @@ func TestOutput_String(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, o) - assert.Equal(t, "value: 1252788362\nscriptLen: 25\nscript: &76a9148bf10d323ac757268eb715e613cb8e8e1d1793aa88ac\n", o.String()) + assert.Equal(t, "value: 1252788362\nscriptLen: 25\nscript: 76a9148bf10d323ac757268eb715e613cb8e8e1d1793aa88ac\n", o.String()) }) } diff --git a/signaturehash.go b/signaturehash.go index 170c5be7..687f08b1 100644 --- a/signaturehash.go +++ b/signaturehash.go @@ -4,11 +4,11 @@ import ( "encoding/binary" "errors" - "github.com/libsv/go-bt/crypto" + "github.com/libsv/go-bk/crypto" "github.com/libsv/go-bt/sighash" ) -// CalcInputSignatureHash serialized the transcation andreturns the hash digest +// CalcInputSignatureHash serialised the transaction and returns the hash digest // to be signed. BitCoin (SV) uses a different signature hashing algorithm // after the UAHF fork for replay protection. // @@ -22,7 +22,7 @@ func (tx *Tx) CalcInputSignatureHash(inputNumber uint32, sigHashFlag sighash.Fla return crypto.Sha256d(buf), nil } -// CalcInputPreimage serializes the transaction based on the input index and the SIGHASH flag +// CalcInputPreimage serialises the transaction based on the input index and the SIGHASH flag // and returns the preimage before double hashing (SHA256d). // // see https://github.com/bitcoin-sv/bitcoin-sv/blob/master/doc/abc/replay-protected-sighash.md#digest-algorithm @@ -81,7 +81,7 @@ func (tx *Tx) CalcInputPreimage(inputNumber uint32, sigHashFlag sighash.Flag) ([ binary.LittleEndian.PutUint32(oi, in.PreviousTxOutIndex) buf = append(buf, oi...) - // scriptCode of the input (serialized as scripts inside CTxOuts) + // scriptCode of the input (serialised as scripts inside CTxOuts) buf = append(buf, VarInt(uint64(len(*in.PreviousTxScript)))...) buf = append(buf, *in.PreviousTxScript...) diff --git a/signer.go b/signer.go index 67f60479..6b433bd5 100644 --- a/signer.go +++ b/signer.go @@ -1,6 +1,10 @@ package bt -import "github.com/libsv/go-bt/sighash" +import ( + "context" + + "github.com/libsv/go-bt/sighash" +) // Signer interface to allow custom implementations of different signing mechanisms. // Implement the Sign function as shown in InternalSigner, for example. Sign generates @@ -9,8 +13,8 @@ import "github.com/libsv/go-bt/sighash" // signature is deterministic (same message and same key yield the same signature) and // canonical in accordance with RFC6979 and BIP0062. type Signer interface { - Sign(unsignedTx *Tx, index uint32, shf sighash.Flag) (publicKey []byte, signature []byte, err error) - SignHash(hash []byte) (publicKey []byte, signature []byte, err error) + Sign(ctx context.Context, unsignedTx *Tx, index uint32, shf sighash.Flag) (publicKey, signature []byte, err error) + SignHash(ctx context.Context, hash []byte) (publicKey, signature []byte, err error) } // AutoSigner interface to allow custom implementations of different signing mechanisms. @@ -23,7 +27,6 @@ type Signer interface { // To automatically sign, the PublicKey() method must also be implemented in order to // use the public key to check which inputs can be signed for before signing. type AutoSigner interface { - Sign(unsignedTx *Tx, index uint32, shf sighash.Flag) (publicKey []byte, signature []byte, err error) - SignHash(hash []byte) (publicKey []byte, signature []byte, err error) - PublicKey() (publicKey []byte) + Signer + PublicKey(ctx context.Context ) (publicKey []byte) } diff --git a/tx.go b/tx.go index 8e864d69..7e080ab7 100644 --- a/tx.go +++ b/tx.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "fmt" - "github.com/libsv/go-bt/crypto" + "github.com/libsv/go-bk/crypto" ) /* @@ -33,7 +33,7 @@ lock_time if non-zero and sequence numbers are < 0xFFFFFFFF: block height // Tx wraps a bitcoin transaction // -// DO NOT CHANGE ORDER - Optimized memory via malign +// DO NOT CHANGE ORDER - Optimised memory via malign // type Tx struct { Inputs []*Input @@ -50,18 +50,18 @@ func NewTx() *Tx { // NewTxFromString takes a toBytesHelper string representation of a bitcoin transaction // and returns a Tx object. func NewTxFromString(str string) (*Tx, error) { - bytes, err := hex.DecodeString(str) + b, err := hex.DecodeString(str) if err != nil { return nil, err } - return NewTxFromBytes(bytes) + return NewTxFromBytes(b) } // NewTxFromBytes takes an array of bytes, constructs a Tx and returns it. // This function assumes that the byte slice contains exactly 1 transaction. func NewTxFromBytes(b []byte) (*Tx, error) { - tx, used, err := NewTxFromStream((b)) + tx, used, err := NewTxFromStream(b) if err != nil { return nil, err } @@ -168,8 +168,8 @@ func (tx *Tx) TxID() string { return hex.EncodeToString(ReverseBytes(crypto.Sha256d(tx.ToBytes()))) } -// ToString encodes the transaction into a hex string. -func (tx *Tx) ToString() string { +// String encodes the transaction into a hex string. +func (tx *Tx) String() string { return hex.EncodeToString(tx.ToBytes()) } diff --git a/tx_test.go b/tx_test.go index cbeecc83..331e09e7 100644 --- a/tx_test.go +++ b/tx_test.go @@ -1,11 +1,12 @@ package bt_test import ( + "context" "encoding/hex" "reflect" "testing" - "github.com/bitcoinsv/bsvutil" + . "github.com/libsv/go-bk/wif" "github.com/libsv/go-bt" "github.com/libsv/go-bt/bscript" "github.com/stretchr/testify/assert" @@ -201,12 +202,12 @@ func TestTx_CreateTx(t *testing.T) { err = tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") + var wif *WIF + wif, err = DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") assert.NoError(t, err) assert.NotNil(t, wif) - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) } @@ -234,12 +235,12 @@ func TestTx_HasDataOutputs(t *testing.T) { err = tx.AddOpReturnPartsOutput(ops) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") + var wif *WIF + wif, err = DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") assert.NoError(t, err) assert.NotNil(t, wif) - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) assert.Equal(t, true, tx.HasDataOutputs()) @@ -259,12 +260,12 @@ func TestTx_HasDataOutputs(t *testing.T) { err = tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") + var wif *WIF + wif, err = DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") assert.NoError(t, err) assert.NotNil(t, wif) - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) assert.Equal(t, false, tx.HasDataOutputs()) diff --git a/txchange.go b/txchange.go index 6778dbd1..2051b39e 100644 --- a/txchange.go +++ b/txchange.go @@ -18,45 +18,83 @@ func (tx *Tx) ChangeToAddress(addr string, f []*Fee) error { } // Change calculates the amount of fees needed to cover the transaction -// and adds the left over change in a new output using the script provided. +// and adds the left over change in a new output using the script provided. func (tx *Tx) Change(s *bscript.Script, f []*Fee) error { + available, hasChange, err := tx.change(s, f, true) + if err != nil { + return err + } + if hasChange { + // add rest of available sats to the change output + tx.Outputs[len(tx.Outputs)-1].Satoshis = available + } + return nil +} + +// ChangeToOutput will calculate fees and add them to an output at the index specified (0 based). +// If an invalid index is supplied and error is returned. +func (tx *Tx) ChangeToOutput(index uint, f []*Fee) error { + if int(index) > len(tx.Outputs)-1 { + return errors.New("index is greater than number of inputs in transaction") + } + available, hasChange, err := tx.change(tx.Outputs[index].LockingScript, f, false) + if err != nil { + return err + } + if hasChange { + tx.Outputs[index].Satoshis += available + } + return nil +} +// CalculateFee will return the amount of fees the current transaction will +// require. +func (tx *Tx) CalculateFee(f []*Fee) (uint64, error) { + total := tx.TotalInputSatoshis() - tx.TotalOutputSatoshis() + sats, _, err := tx.change(nil, f, false) + if err != nil { + return 0, err + } + return total - sats, nil +} + +// change will return the amount of satoshis to add to an input after fees are removed. +// True will be returned if change has been added. +func (tx *Tx) change(s *bscript.Script, f []*Fee, newOutput bool) (uint64, bool, error) { inputAmount := tx.TotalInputSatoshis() outputAmount := tx.TotalOutputSatoshis() if inputAmount < outputAmount { - return errors.New("satoshis inputted to the tx are less than the outputted satoshis") + return 0, false, errors.New("satoshis inputted to the tx are less than the outputted satoshis") } available := inputAmount - outputAmount standardFees, err := ExtractStandardFee(f) if err != nil { - return err + return 0, false, err } if !tx.canAddChange(available, standardFees) { - return nil + return 0, false, err + } + if newOutput { + tx.AddOutput(&Output{Satoshis: 0, LockingScript: s}) } - - tx.AddOutput(&Output{Satoshis: 0, LockingScript: s}) var preSignedFeeRequired uint64 if preSignedFeeRequired, err = tx.getPreSignedFeeRequired(f); err != nil { - return err + return 0, false, err } var expectedUnlockingScriptFees uint64 if expectedUnlockingScriptFees, err = tx.getExpectedUnlockingScriptFees(f); err != nil { - return err + return 0, false, err } available -= preSignedFeeRequired + expectedUnlockingScriptFees - // add rest of available sats to the change output - tx.Outputs[len(tx.Outputs)-1].Satoshis = available - - return nil + return available, true, nil } func (tx *Tx) canAddChange(available uint64, standardFees *Fee) bool { diff --git a/txchange_test.go b/txchange_test.go index 2431c70b..285dbdb5 100644 --- a/txchange_test.go +++ b/txchange_test.go @@ -1,9 +1,11 @@ package bt_test import ( + "context" + "errors" "testing" - "github.com/bitcoinsv/bsvutil" + . "github.com/libsv/go-bk/wif" "github.com/libsv/go-bt" "github.com/stretchr/testify/assert" ) @@ -51,7 +53,7 @@ func TestTx_ChangeToAddress(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, tx.OutputCount()) - assert.Equal(t, "76a914a7a1a7fd7d279b57b84e596cbbf82608efdb441a88ac", tx.Outputs[0].LockingScript.ToString()) + assert.Equal(t, "76a914a7a1a7fd7d279b57b84e596cbbf82608efdb441a88ac", tx.Outputs[0].LockingScript.String()) }) } @@ -76,15 +78,15 @@ func TestTx_Change(t *testing.T) { err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.DefaultFees()) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") + var wif *WIF + wif, err = DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") assert.NoError(t, err) assert.NotNil(t, wif) - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) - assert.Equal(t, expectedTx.ToString(), tx.ToString()) + assert.Equal(t, expectedTx.String(), tx.String()) }) t.Run("change output is added correctly - fee removed", func(t *testing.T) { @@ -102,12 +104,12 @@ func TestTx_Change(t *testing.T) { err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.DefaultFees()) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") + var wif *WIF + wif, err = DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") assert.NoError(t, err) assert.NotNil(t, wif) - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) // Correct fee for the tx @@ -144,17 +146,17 @@ func TestTx_Change(t *testing.T) { err = tx.ChangeToAddress("1D7gaZJo3vPn2Ks3PH694W9P8UVYLNh2jY", bt.DefaultFees()) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") + var wif *WIF + wif, err = DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") assert.NoError(t, err) assert.NotNil(t, wif) - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) assert.Equal(t, "0100000001760595866e99c1ce920197844740f5598b34763878696371d41b3a7c0a65b0b7000000006a473044022054ec562aefb1d5b4483906b0fa728a951b81944d8a82c2be1c6e7ea67e8c34b702200c2657c5d2a744d138bfb4204c579de20e984d680695823cba3fb7a0346b3c19412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff03f4010000000000001976a9147a1980655efbfec416b2b0c663a7b3ac0b6a25d288ac000000000000000011006a02686903686f770361726503796f757a010000000000001976a91484e50b300b009833b297dc671817c79b5459da1d88ac00000000", - tx.ToString(), + tx.String(), ) feePaid := tx.TotalInputSatoshis() - tx.TotalOutputSatoshis() @@ -181,15 +183,15 @@ func TestTx_Change(t *testing.T) { err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.DefaultFees()) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") + var wif *WIF + wif, err = DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") assert.NoError(t, err) assert.NotNil(t, wif) - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) - assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006a473044022049ee0c0f26c00e6a6b3af5990fc8296c66eab3e3e42ab075069b89b1be6fefec02206079e49dd8c9e1117ef06fbe99714d822620b1f0f5d19f32a1128f5d29b7c3c4412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff01a0083d00000000001976a914af2590a45ae401651fdbdf59a76ad43d1862534088ac00000000", tx.ToString()) + assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006a473044022049ee0c0f26c00e6a6b3af5990fc8296c66eab3e3e42ab075069b89b1be6fefec02206079e49dd8c9e1117ef06fbe99714d822620b1f0f5d19f32a1128f5d29b7c3c4412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff01a0083d00000000001976a914af2590a45ae401651fdbdf59a76ad43d1862534088ac00000000", tx.String()) assert.Equal(t, uint64(3999904), tx.Outputs[0].Satoshis) }) @@ -214,15 +216,15 @@ func TestTx_Change(t *testing.T) { err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.DefaultFees()) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") + var wif *WIF + wif, err = DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") assert.NoError(t, err) assert.NotNil(t, wif) - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) - assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006a47304402206bbb4b23349bdf86e6fbc9067226e9a7b15c977fa530999b39cd0a6d9c83360d02202dd8ffdc610e58b3fc92b44400d99e38c78866765f31acb40d98007a52e7a826412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff0240420f00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88acc0c62d00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88ac00000000", tx.ToString()) + assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006a47304402206bbb4b23349bdf86e6fbc9067226e9a7b15c977fa530999b39cd0a6d9c83360d02202dd8ffdc610e58b3fc92b44400d99e38c78866765f31acb40d98007a52e7a826412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff0240420f00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88acc0c62d00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88ac00000000", tx.String()) assert.Equal(t, uint64(1000000), tx.Outputs[0].Satoshis) assert.Equal(t, uint64(3000000), tx.Outputs[1].Satoshis) @@ -248,15 +250,15 @@ func TestTx_Change(t *testing.T) { err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.DefaultFees()) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") + var wif *WIF + wif, err = DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") assert.NoError(t, err) assert.NotNil(t, wif) - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) - assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006b483045022100fd07316603e9abf393e695192e8ce1e7f808d2735cc57039109a2210ad32d9a7022000e301e2a988b23ab3872b041df8b6eb0315238e0918944cbaf8b6abdde75cac412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff023b420f00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88acc0c62d00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88ac00000000", tx.ToString()) + assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006b483045022100fd07316603e9abf393e695192e8ce1e7f808d2735cc57039109a2210ad32d9a7022000e301e2a988b23ab3872b041df8b6eb0315238e0918944cbaf8b6abdde75cac412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff023b420f00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88acc0c62d00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88ac00000000", tx.String()) // todo: expected the pay-to inputs to change based on the fee :P @@ -285,17 +287,154 @@ func TestTx_Change(t *testing.T) { err = tx.ChangeToAddress("1BxGFoRPSFgYxoAStEncL6HuELqPkV3JVj", bt.DefaultFees()) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("5JXAjNX7cbiWvmkdnj1EnTKPChauttKAJibXLm8tqWtDhXrRbKz") + var wif *WIF + wif, err = DecodeWIF("5JXAjNX7cbiWvmkdnj1EnTKPChauttKAJibXLm8tqWtDhXrRbKz") assert.NoError(t, err) assert.NotNil(t, wif) - is, err := tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) assert.ElementsMatch(t, []int{0, 1}, is) assert.Equal(t, 2, len(is)) - assert.Equal(t, "01000000028ee20a442cdbcc9f9f927d9c2c9370e611675ebc24c064e8e94508ec8eca889e000000006b483045022100fa52a44cd8010ba646a8df6bac6e5e8aa93f24439521c2ce1c8fe6550e73c1750220636e30d757702a6777d8310090962d4bac2b3fd634127856d51b184f5c702c8f4121034aaeabc056f33fd960d1e43fc8a0672723af02f275e54c31381af66a334634caffffffff42eaf7bdddc797a0beb97717ff8846f03c963fb5fe15a2b555b9cbd477b0254e000000006b483045022100c201fd55ef33525b3eb0557fac77408b8ec7f6ea5b00d08512df105172f992d60220753b21519a416dcbeaf1a501d9c36de2aea9c83c6d258320500371819d0758e14121034aaeabc056f33fd960d1e43fc8a0672723af02f275e54c31381af66a334634caffffffff01c62b0000000000001976a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac00000000", tx.ToString()) + assert.Equal(t, "01000000028ee20a442cdbcc9f9f927d9c2c9370e611675ebc24c064e8e94508ec8eca889e000000006b483045022100fa52a44cd8010ba646a8df6bac6e5e8aa93f24439521c2ce1c8fe6550e73c1750220636e30d757702a6777d8310090962d4bac2b3fd634127856d51b184f5c702c8f4121034aaeabc056f33fd960d1e43fc8a0672723af02f275e54c31381af66a334634caffffffff42eaf7bdddc797a0beb97717ff8846f03c963fb5fe15a2b555b9cbd477b0254e000000006b483045022100c201fd55ef33525b3eb0557fac77408b8ec7f6ea5b00d08512df105172f992d60220753b21519a416dcbeaf1a501d9c36de2aea9c83c6d258320500371819d0758e14121034aaeabc056f33fd960d1e43fc8a0672723af02f275e54c31381af66a334634caffffffff01c62b0000000000001976a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac00000000", tx.String()) }) } + +func TestTx_ChangeToOutput(t *testing.T) { + tests := map[string]struct { + tx *bt.Tx + index uint + fees []*bt.Fee + expOutputTotal uint64 + expChangeOutput uint64 + err error + }{ + "no change to add should return no change output": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.From( + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 1000)) + return tx + }(), + index: 0, + fees: bt.DefaultFees(), + expOutputTotal: 1000, + expChangeOutput: 1000, + err: nil, + }, "change to add should add change to output": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.From( + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + return tx + }(), + index: 0, + fees: bt.DefaultFees(), + expOutputTotal: 904, + expChangeOutput: 904, + err: nil, + }, "change to add should add change to specified output": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.From( + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2500)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + return tx + }(), + index: 3, + fees: bt.DefaultFees(), + expOutputTotal: 2353, + expChangeOutput: 853, + err: nil, + }, "index out of range should return error": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.From( + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + return tx + }(), + index: 1, + fees: bt.DefaultFees(), + err: errors.New("index is greater than number of inputs in transaction"), + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + err := test.tx.ChangeToOutput(test.index, test.fees) + if test.err != nil { + assert.Error(t, err) + assert.Equal(t, test.err, err) + return + } + assert.Equal(t, test.expOutputTotal, test.tx.TotalOutputSatoshis()) + assert.Equal(t, test.expChangeOutput, test.tx.Outputs[test.index].Satoshis) + }) + } +} + +func TestTx_CalculateChange(t *testing.T) { + tests := map[string]struct { + tx *bt.Tx + fees []*bt.Fee + expFees uint64 + err error + }{ + "Transaction with one input one output should return 96": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.From( + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + return tx + }(), + fees: bt.DefaultFees(), + expFees: 96, + }, "Transaction with one input 4 outputs should return 147": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.From( + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2500)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + return tx + }(), + fees: bt.DefaultFees(), + expFees: 147, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + fee, err := test.tx.CalculateFee(test.fees) + assert.Equal(t, test.err, err) + assert.Equal(t, test.expFees, fee) + }) + } +} diff --git a/txinput.go b/txinput.go index 80e409cb..35904cc8 100644 --- a/txinput.go +++ b/txinput.go @@ -6,8 +6,9 @@ import ( "encoding/hex" "fmt" + "github.com/libsv/go-bk/crypto" + "github.com/libsv/go-bt/bscript" - "github.com/libsv/go-bt/crypto" ) // NewInputFromBytes returns a transaction input from the bytes provided. @@ -49,7 +50,6 @@ func (tx *Tx) addInput(input *Input) { // AddInputFromTx will add all outputs of given previous transaction // that match a specific public key to your transaction. func (tx *Tx) AddInputFromTx(pvsTx *Tx, matchPK []byte) error { - for i, utxo := range pvsTx.Outputs { utxoPkHASH160, err := utxo.LockingScript.PublicKeyHash() if err != nil { @@ -57,8 +57,7 @@ func (tx *Tx) AddInputFromTx(pvsTx *Tx, matchPK []byte) error { } if bytes.Equal(utxoPkHASH160, crypto.Hash160(matchPK)) { - err = tx.From(pvsTx.TxID(), uint32(i), utxo.LockingScriptHexString(), utxo.Satoshis) - if err != nil { + if err := tx.From(pvsTx.TxID(), uint32(i), utxo.LockingScriptHexString(), utxo.Satoshis); err != nil { return err } } @@ -68,7 +67,7 @@ func (tx *Tx) AddInputFromTx(pvsTx *Tx, matchPK []byte) error { } // From adds a new input to the transaction from the specified UTXO fields, using the default -// finalized sequence number (0xFFFFFFFF). If you want a different nSeq, change it manually +// finalised sequence number (0xFFFFFFFF). If you want a different nSeq, change it manually // afterwards. func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, satoshis uint64) error { pts, err := bscript.NewFromHexString(prevTxLockingScript) @@ -86,7 +85,7 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat PreviousTxOutIndex: vout, PreviousTxSatoshis: satoshis, PreviousTxScript: pts, - SequenceNumber: DefaultSequenceNumber, // use default finalized sequence number + SequenceNumber: DefaultSequenceNumber, // use default finalised sequence number }) return nil diff --git a/txinput_test.go b/txinput_test.go index d112a13c..416d1781 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -76,6 +76,6 @@ func TestTx_From(t *testing.T) { assert.Equal(t, uint32(0), inputs[0].PreviousTxOutIndex) assert.Equal(t, uint64(4000000), inputs[0].PreviousTxSatoshis) assert.Equal(t, bt.DefaultSequenceNumber, inputs[0].SequenceNumber) - assert.Equal(t, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", inputs[0].PreviousTxScript.ToString()) + assert.Equal(t, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", inputs[0].PreviousTxScript.String()) }) } diff --git a/txoutput.go b/txoutput.go index 5cd46092..32092e11 100644 --- a/txoutput.go +++ b/txoutput.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/libsv/go-bt/bscript" - "github.com/libsv/go-bt/crypto" + "github.com/libsv/go-bk/crypto" ) // NewOutputFromBytes returns a transaction Output from the bytes provided @@ -99,7 +99,6 @@ func (tx *Tx) AddP2PKHOutputFromAddress(addr string, satoshis uint64) error { // AddHashPuzzleOutput makes an output to a hash puzzle + PKH with a value. func (tx *Tx) AddHashPuzzleOutput(secret, publicKeyHash string, satoshis uint64) error { - publicKeyHashBytes, err := hex.DecodeString(publicKeyHash) if err != nil { return err @@ -113,15 +112,15 @@ func (tx *Tx) AddHashPuzzleOutput(secret, publicKeyHash string, satoshis uint64) if err = s.AppendPushData(secretBytesHash); err != nil { return err } - s.AppendOpCode(bscript.OpEQUALVERIFY) - s.AppendOpCode(bscript.OpDUP) - s.AppendOpCode(bscript.OpHASH160) + s.AppendOpCode(bscript.OpEQUALVERIFY). + AppendOpCode(bscript.OpDUP). + AppendOpCode(bscript.OpHASH160) if err = s.AppendPushData(publicKeyHashBytes); err != nil { return err } - s.AppendOpCode(bscript.OpEQUALVERIFY) - s.AppendOpCode(bscript.OpCHECKSIG) + s.AppendOpCode(bscript.OpEQUALVERIFY). + AppendOpCode(bscript.OpCHECKSIG) tx.AddOutput(&Output{ Satoshis: satoshis, @@ -149,7 +148,6 @@ func (tx *Tx) AddOpReturnPartsOutput(data [][]byte) error { if err != nil { return err } - tx.AddOutput(o) return nil } diff --git a/txsign.go b/txsign.go index 6d116169..cd7e8507 100644 --- a/txsign.go +++ b/txsign.go @@ -1,11 +1,12 @@ package bt import ( + "context" "encoding/hex" "fmt" "github.com/libsv/go-bt/bscript" - "github.com/libsv/go-bt/crypto" + "github.com/libsv/go-bk/crypto" "github.com/libsv/go-bt/sighash" ) @@ -13,17 +14,15 @@ import ( // It takes a Signed interface as a parameter so that different // signing implementations can be used to sign the transaction - // for example internal/local or external signing (hardware wallet). -func (tx *Tx) Sign(s Signer, index uint32, shf sighash.Flag) error { +func (tx *Tx) Sign(ctx context.Context, s Signer, index uint32, shf sighash.Flag) error { if shf == 0 { shf = sighash.AllForkID } - - pubkey, sig, err := s.Sign(tx, index, shf) + pubKey, sig, err := s.Sign(ctx, tx, index, shf) if err != nil { return err } - - return tx.ApplyP2PKHUnlockingScript(index, pubkey, sig, shf) + return tx.ApplyP2PKHUnlockingScript(index, pubKey, sig, shf) } // SignHash is used to sign the transaction at a specific input index. @@ -35,7 +34,7 @@ func (tx *Tx) Sign(s Signer, index uint32, shf sighash.Flag) error { // take the final signature hash to be signed so will need to trust that // it is getting the right hash to sign as there no way to verify that // it is signing the right hash. -func (tx *Tx) SignHash(s Signer, index uint32, shf sighash.Flag) error { +func (tx *Tx) SignHash(ctx context.Context, s Signer, index uint32, shf sighash.Flag) error { if shf == 0 { shf = sighash.AllForkID } @@ -45,12 +44,12 @@ func (tx *Tx) SignHash(s Signer, index uint32, shf sighash.Flag) error { return err } - pubkey, sig, err := s.SignHash(sh) + pubKey, sig, err := s.SignHash(ctx, sh) if err != nil { return err } - return tx.ApplyP2PKHUnlockingScript(index, pubkey, sig, shf) + return tx.ApplyP2PKHUnlockingScript(index, pubKey, sig, shf) } // ApplyP2PKHUnlockingScript applies a script to the transaction at a specific index in @@ -80,19 +79,18 @@ func (tx *Tx) ApplyUnlockingScript(index uint32, s *bscript.Script) error { // It takes a Signed interface as a parameter so that different // signing implementations can be used to sign the transaction - // for example internal/local or external signing. -func (tx *Tx) SignAuto(s AutoSigner) (inputsSigned []int, err error) { - +func (tx *Tx) SignAuto(ctx context.Context, s AutoSigner) (inputsSigned []int, err error) { shf := sighash.AllForkID // use SIGHASHALLFORFORKID to sign automatically for i, in := range tx.Inputs { pubKeyHash, _ := in.PreviousTxScript.PublicKeyHash() // doesn't matter if returns error (not p2pkh) pubKeyHashStr := hex.EncodeToString(pubKeyHash) - pubKeyHashStrFromPriv := hex.EncodeToString(crypto.Hash160(s.PublicKey())) + pubKeyHashStrFromPriv := hex.EncodeToString(crypto.Hash160(s.PublicKey(ctx))) // check if able to sign (public key matches pubKeyHash in script) if pubKeyHashStr == pubKeyHashStrFromPriv { - if err = tx.Sign(s, uint32(i), shf); err != nil { + if err = tx.Sign(ctx, s, uint32(i), shf); err != nil { return } inputsSigned = append(inputsSigned, i) diff --git a/txsign_test.go b/txsign_test.go index 36b62010..f4b4de8e 100644 --- a/txsign_test.go +++ b/txsign_test.go @@ -1,9 +1,10 @@ package bt_test import ( + "context" "testing" - "github.com/bitcoinsv/bsvutil" + . "github.com/libsv/go-bk/wif" "github.com/libsv/go-bt" "github.com/stretchr/testify/assert" ) @@ -29,29 +30,29 @@ func TestTx_SignAuto(t *testing.T) { err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.DefaultFees()) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") + var wif *WIF + wif, err = DecodeWIF("L3MhnEn1pLWcggeYLk9jdkvA2wUK1iWwwrGkBbgQRqv6HPCdRxuw") assert.NoError(t, err) assert.NotNil(t, wif) - rawTxBefore := tx.ToString() + rawTxBefore := tx.String() - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) - assert.NotEqual(t, rawTxBefore, tx.ToString()) + assert.NotEqual(t, rawTxBefore, tx.String()) }) t.Run("no input or output", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - rawTxBefore := tx.ToString() + rawTxBefore := tx.String() - _, err := tx.SignAuto(&bt.LocalSigner{PrivateKey: nil}) + _, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: nil}) assert.NoError(t, err) - assert.Equal(t, rawTxBefore, tx.ToString()) + assert.Equal(t, rawTxBefore, tx.String()) }) t.Run("valid tx (wrong wif)", func(t *testing.T) { @@ -68,15 +69,15 @@ func TestTx_SignAuto(t *testing.T) { err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.DefaultFees()) assert.NoError(t, err) - var wif *bsvutil.WIF - wif, err = bsvutil.DecodeWIF("5KgHn2qiftW5LQgCYFtkbrLYB1FuvisDtacax8NCvumw3UTKdcP") + var wif *WIF + wif, err = DecodeWIF("5KgHn2qiftW5LQgCYFtkbrLYB1FuvisDtacax8NCvumw3UTKdcP") assert.NoError(t, err) assert.NotNil(t, wif) // No signature, wrong wif - rawTxBefore := tx.ToString() - _, err = tx.SignAuto(&bt.LocalSigner{PrivateKey: wif.PrivKey}) + rawTxBefore := tx.String() + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) assert.NoError(t, err) - assert.Equal(t, rawTxBefore, tx.ToString()) + assert.Equal(t, rawTxBefore, tx.String()) }) }