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

feat(SPV-936): upgrade to go-sdk #250

Merged
merged 15 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 63 additions & 89 deletions authentication.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package walletclient

import (
"encoding/hex"
"encoding/base64"
"fmt"
"net/http"
"time"

bip32 "github.com/bitcoin-sv/go-sdk/compat/bip32"
bsm "github.com/bitcoin-sv/go-sdk/compat/bsm"
ec "github.com/bitcoin-sv/go-sdk/primitives/ec"
script "github.com/bitcoin-sv/go-sdk/script"
trx "github.com/bitcoin-sv/go-sdk/transaction"
sighash "github.com/bitcoin-sv/go-sdk/transaction/sighash"
"github.com/bitcoin-sv/go-sdk/transaction/template/p2pkh"

"github.com/bitcoin-sv/spv-wallet-go-client/utils"
"github.com/bitcoin-sv/spv-wallet/models"
"github.com/bitcoinschema/go-bitcoin/v2"
"github.com/libsv/go-bk/bec"
"github.com/libsv/go-bk/bip32"
"github.com/libsv/go-bt/v2"
"github.com/libsv/go-bt/v2/bscript"
"github.com/libsv/go-bt/v2/sighash"
)

// SetSignature will set the signature on the header for the request
Expand All @@ -33,102 +35,72 @@ func setSignature(header *http.Header, xPriv *bip32.ExtendedKey, bodyString stri
}

// GetSignedHex will sign all the inputs using the given xPriv key
func GetSignedHex(dt *models.DraftTransaction, xPriv *bip32.ExtendedKey) (signedHex string, err error) {
var tx *bt.Tx
if tx, err = bt.NewTxFromString(dt.Hex); err != nil {
return
func GetSignedHex(dt *models.DraftTransaction, xPriv *bip32.ExtendedKey) (string, error) {
// Create transaction from hex
tx, err := trx.NewTransactionFromHex(dt.Hex)

// we need to reset the inputs as we are going to add them via tx.AddInputFrom (ts-sdk method) and then sign
tx.Inputs = make([]*trx.TransactionInput, 0)
chris-4chain marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return "", err
}

// Enrich inputs
for index, draftInput := range dt.Configuration.Inputs {
tx.Inputs[index].PreviousTxSatoshis = draftInput.Satoshis

dst := draftInput.Destination
if err = setPreviousTxScript(tx, uint32(index), &dst); err != nil {
return
for _, draftInput := range dt.Configuration.Inputs {
lockingScript, err := prepareLockingScript(&draftInput.Destination)
if err != nil {
return "", err
}

if err = setUnlockingScript(tx, uint32(index), xPriv, &dst); err != nil {
return
unlockScript, err := prepareUnlockingScript(xPriv, &draftInput.Destination)
if err != nil {
return "", err
}
}

// Return the signed hex
signedHex = tx.String()
return
}

func setPreviousTxScript(tx *bt.Tx, inputIndex uint32, dst *models.Destination) (err error) {
var ls *bscript.Script
if ls, err = bscript.NewFromHexString(dst.LockingScript); err != nil {
return
tx.AddInputFrom(draftInput.TransactionID, draftInput.OutputIndex, lockingScript.String(), draftInput.Satoshis, unlockScript)
}

tx.Inputs[inputIndex].PreviousTxScript = ls
return
tx.Sign()

return tx.String(), nil
}

func setUnlockingScript(tx *bt.Tx, inputIndex uint32, xPriv *bip32.ExtendedKey, dst *models.Destination) (err error) {
var key *bec.PrivateKey
if key, err = getDerivedKeyForDestination(xPriv, dst); err != nil {
return
}
func prepareLockingScript(dst *models.Destination) (*script.Script, error) {
return script.NewFromHex(dst.LockingScript)
chris-4chain marked this conversation as resolved.
Show resolved Hide resolved
}

var s *bscript.Script
if s, err = getUnlockingScript(tx, inputIndex, key); err != nil {
return
func prepareUnlockingScript(xPriv *bip32.ExtendedKey, dst *models.Destination) (*p2pkh.P2PKH, error) {
key, err := getDerivedKeyForDestination(xPriv, dst)
if err != nil {
return nil, err
}

tx.Inputs[inputIndex].UnlockingScript = s
return
return getUnlockingScript(key)
}

func getDerivedKeyForDestination(xPriv *bip32.ExtendedKey, dst *models.Destination) (key *bec.PrivateKey, err error) {
func getDerivedKeyForDestination(xPriv *bip32.ExtendedKey, dst *models.Destination) (*ec.PrivateKey, error) {
// Derive the child key (m/chain/num)
var derivedKey *bip32.ExtendedKey
if derivedKey, err = bitcoin.GetHDKeyByPath(xPriv, dst.Chain, dst.Num); err != nil {
return
derivedKey, err := bip32.GetHDKeyByPath(xPriv, dst.Chain, dst.Num)
if err != nil {
return nil, err
}

// Derive key for paymail destination (m/chain/num/paymailNum)
// Handle paymail destination derivation if applicable
if dst.PaymailExternalDerivationNum != nil {
if derivedKey, err = derivedKey.Child(
*dst.PaymailExternalDerivationNum,
); err != nil {
return
derivedKey, err = derivedKey.Child(*dst.PaymailExternalDerivationNum)
if err != nil {
return nil, err
}
}

if key, err = bitcoin.GetPrivateKeyFromHDKey(derivedKey); err != nil {
return
}

return
// Get the private key from the derived key
return bip32.GetPrivateKeyFromHDKey(derivedKey)
}

// GetUnlockingScript will generate an unlocking script
func getUnlockingScript(tx *bt.Tx, inputIndex uint32, privateKey *bec.PrivateKey) (*bscript.Script, error) {
// Generate unlocking script using private key
func getUnlockingScript(privateKey *ec.PrivateKey) (*p2pkh.P2PKH, error) {
sigHashFlags := sighash.AllForkID

sigHash, err := tx.CalcInputSignatureHash(inputIndex, sigHashFlags)
if err != nil {
return nil, err
}

var sig *bec.Signature
if sig, err = privateKey.Sign(sigHash); err != nil {
return nil, err
}

pubKey := privateKey.PubKey().SerialiseCompressed()
signature := sig.Serialise()

var script *bscript.Script
if script, err = bscript.NewP2PKHUnlockingScript(pubKey, signature, sigHashFlags); err != nil {
return nil, err
}

return script, nil
return p2pkh.Unlock(privateKey, &sigHashFlags)
}

// createSignature will create a signature for the given key & body contents
Expand All @@ -141,7 +113,7 @@ func createSignature(xPriv *bip32.ExtendedKey, bodyString string) (payload *mode

// Get the xPub
payload = new(models.AuthPayload)
if payload.XPub, err = bitcoin.GetExtendedPublicKey(
if payload.XPub, err = bip32.GetExtendedPublicKey(
xPriv,
); err != nil { // Should never error if key is correct
return
Expand All @@ -161,16 +133,16 @@ func createSignature(xPriv *bip32.ExtendedKey, bodyString string) (payload *mode
return
}

var privateKey *bec.PrivateKey
if privateKey, err = bitcoin.GetPrivateKeyFromHDKey(key); err != nil {
var privateKey *ec.PrivateKey
if privateKey, err = bip32.GetPrivateKeyFromHDKey(key); err != nil {
return // Should never error if key is correct
}

return createSignatureCommon(payload, bodyString, privateKey)
}

// createSignatureCommon will create a signature
func createSignatureCommon(payload *models.AuthPayload, bodyString string, privateKey *bec.PrivateKey) (*models.AuthPayload, error) {
func createSignatureCommon(payload *models.AuthPayload, bodyString string, privateKey *ec.PrivateKey) (*models.AuthPayload, error) {
// Create the auth header hash
payload.AuthHash = utils.Hash(bodyString)

Expand All @@ -183,21 +155,23 @@ func createSignatureCommon(payload *models.AuthPayload, bodyString string, priva
}

// Signature, using bitcoin signMessage
var err error
if payload.Signature, err = bitcoin.SignMessage(
hex.EncodeToString(privateKey.Serialise()),
sigBytes, err := bsm.SignMessage(
privateKey,
getSigningMessage(key, payload),
true,
); err != nil {
)
if err != nil {
return nil, err
}

payload.Signature = base64.StdEncoding.EncodeToString(sigBytes)

return payload, nil
}

// getSigningMessage will build the signing message string
func getSigningMessage(xPub string, auth *models.AuthPayload) string {
return fmt.Sprintf("%s%s%s%d", xPub, auth.AuthHash, auth.AuthNonce, auth.AuthTime)
// getSigningMessage will build the signing message byte array
func getSigningMessage(xPub string, auth *models.AuthPayload) []byte {
message := fmt.Sprintf("%s%s%s%d", xPub, auth.AuthHash, auth.AuthNonce, auth.AuthTime)
return []byte(message) // Convert string to byte array
chris-4chain marked this conversation as resolved.
Show resolved Hide resolved
}

func setSignatureHeaders(header *http.Header, authData *models.AuthPayload) {
Expand Down
29 changes: 12 additions & 17 deletions client_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import (
"net/http"
"net/url"

"github.com/bitcoinschema/go-bitcoin/v2"
"github.com/libsv/go-bk/bec"
"github.com/libsv/go-bk/wif"
bip32 "github.com/bitcoin-sv/go-sdk/compat/bip32"
ec "github.com/bitcoin-sv/go-sdk/primitives/ec"

"github.com/pkg/errors"
)

Expand All @@ -23,7 +23,7 @@ type xPrivConf struct {

func (w *xPrivConf) Configure(c *WalletClient) {
var err error
if c.xPriv, err = bitcoin.GenerateHDKeyFromString(w.XPrivString); err != nil {
if c.xPriv, err = bip32.GenerateHDKeyFromString(w.XPrivString); err != nil {
c.xPriv = nil
}
}
Expand All @@ -35,7 +35,7 @@ type xPubConf struct {

func (w *xPubConf) Configure(c *WalletClient) {
var err error
if c.xPub, err = bitcoin.GetHDKeyFromExtendedPublicKey(w.XPubString); err != nil {
if c.xPub, err = bip32.GetHDKeyFromExtendedPublicKey(w.XPubString); err != nil {
c.xPub = nil
}

Expand All @@ -60,7 +60,7 @@ type adminKeyConf struct {

func (w *adminKeyConf) Configure(c *WalletClient) {
var err error
c.adminXPriv, err = bitcoin.GenerateHDKeyFromString(w.AdminKeyString)
c.adminXPriv, err = bip32.GenerateHDKeyFromString(w.AdminKeyString)
if err != nil {
c.adminXPriv = nil
}
Expand Down Expand Up @@ -101,18 +101,13 @@ func (w *signRequest) Configure(c *WalletClient) {
c.signRequest = w.Sign
}

// initializeAccessKey handles the specific initialization of the access key.
func (w *accessKeyConf) initializeAccessKey() (*bec.PrivateKey, error) {
var err error
var privateKey *bec.PrivateKey
var decodedWIF *wif.WIF

if decodedWIF, err = wif.DecodeWIF(w.AccessKeyString); err != nil {
if privateKey, err = bitcoin.PrivateKeyFromString(w.AccessKeyString); err != nil {
return nil, errors.Wrap(err, "failed to decode access key")
func (w *accessKeyConf) initializeAccessKey() (*ec.PrivateKey, error) {
privateKey, err := ec.PrivateKeyFromWif(w.AccessKeyString)
if err != nil {
privateKey, _ = ec.PrivateKeyFromHex(w.AccessKeyString)
chris-4chain marked this conversation as resolved.
Show resolved Hide resolved
if privateKey == nil {
return nil, errors.New("failed to decode access key")
}
} else {
privateKey = decodedWIF.PrivKey
}

return privateKey, nil
Expand Down
7 changes: 2 additions & 5 deletions examples/go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 4 additions & 12 deletions examples/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading