diff --git a/examples/remote-signing-server/go.mod b/examples/remote-signing-server/go.mod index 2a2230b..f6d7879 100644 --- a/examples/remote-signing-server/go.mod +++ b/examples/remote-signing-server/go.mod @@ -13,6 +13,7 @@ require ( github.com/btcsuite/btcd v0.24.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/btcsuite/btcd/btcutil/psbt v1.1.9 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/bytedance/sonic v1.9.1 // indirect diff --git a/examples/remote-signing-server/go.sum b/examples/remote-signing-server/go.sum index 63e7d53..3b1bd88 100644 --- a/examples/remote-signing-server/go.sum +++ b/examples/remote-signing-server/go.sum @@ -12,6 +12,8 @@ github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9Ur github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil/psbt v1.1.9 h1:UmfOIiWMZcVMOLaN+lxbbLSuoINGS1WmK1TZNI0b4yk= +github.com/btcsuite/btcd/btcutil/psbt v1.1.9/go.mod h1:ehBEvU91lxSlXtA+zZz3iFYx7Yq9eqnKx4/kSrnsvMY= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= diff --git a/examples/remote-signing-server/server.go b/examples/remote-signing-server/server.go index 4cc97cf..02d2a5a 100644 --- a/examples/remote-signing-server/server.go +++ b/examples/remote-signing-server/server.go @@ -66,7 +66,7 @@ func main() { case objects.WebhookEventTypeRemoteSigning: if config.RespondDirectly { resp, err := remotesigning.GraphQLResponseForRemoteSigningWebhook( - remotesigning.PositiveValidator{}, *event, config.MasterSeed) + remotesigning.HashValidator{}, *event, config.MasterSeed) if err != nil { log.Printf("ERROR: Unable to handle remote signing webhook: %s", err) c.AbortWithStatus(http.StatusInternalServerError) @@ -80,7 +80,7 @@ func main() { } } else { resp, err := remotesigning.HandleRemoteSigningWebhook( - lsClient, remotesigning.PositiveValidator{}, *event, config.MasterSeed) + lsClient, remotesigning.HashValidator{}, *event, config.MasterSeed) if err != nil { log.Printf("ERROR: Unable to handle remote signing webhook: %s", err) c.AbortWithStatus(http.StatusInternalServerError) diff --git a/go.mod b/go.mod index 350fcf5..a8a379f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( require ( github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect - github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/btcsuite/btcd/btcutil v1.1.5 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect @@ -24,6 +24,7 @@ require ( require ( github.com/btcsuite/btcd v0.24.0 + github.com/btcsuite/btcd/btcutil/psbt v1.1.9 github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index ff690ca..9ab1ae5 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9Ur github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil/psbt v1.1.9 h1:UmfOIiWMZcVMOLaN+lxbbLSuoINGS1WmK1TZNI0b4yk= +github.com/btcsuite/btcd/btcutil/psbt v1.1.9/go.mod h1:ehBEvU91lxSlXtA+zZz3iFYx7Yq9eqnKx4/kSrnsvMY= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= diff --git a/remotesigning/validation.go b/remotesigning/validation.go index 3e97a22..afdcb81 100644 --- a/remotesigning/validation.go +++ b/remotesigning/validation.go @@ -1,10 +1,13 @@ package remotesigning import ( + "bytes" "encoding/hex" "errors" "regexp" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/txscript" ) @@ -26,6 +29,63 @@ func GetPaymentHashFromScript(scriptHex string) (*string, error) { if len(match) > 0 { return &match[1], nil } else { - return nil, errors.New("No match found") + return nil, errors.New("no match found") } } + +func CalculateWitnessHash(amount int64, script string, transaction string) (*string, error) { + decodedTx, err := hex.DecodeString(transaction) + if err != nil { + return nil, err + } + + tx, err := btcutil.NewTxFromBytes(decodedTx) + if err != nil { + return nil ,err + } + + decodedScript, err := hex.DecodeString(script) + if err != nil { + return nil ,err + } + + prevOutFetcher := txscript.NewCannedPrevOutputFetcher( + decodedScript, amount, + ) + + txhash := txscript.NewTxSigHashes(tx.MsgTx(), prevOutFetcher) + hash, err := txscript.CalcWitnessSigHash(decodedScript, txhash, txscript.SigHashAll, tx.MsgTx(), 0, amount) + if err != nil { + return nil, err + } + + result := hex.EncodeToString(hash) + + return &result, nil +} + +func CalculateWitnessHashPSBT(transaction string) (*string, error) { + transactionBytes, err := hex.DecodeString(transaction) + if err != nil { + return nil, err + } + // Reader for the PSBT. + psbtBytes := []byte(transactionBytes) + r := bytes.NewReader(psbtBytes) + + // Create instance of a PSBT. + p, err := psbt.NewFromRawBytes(r, false) + if err != nil { + return nil, err + } + prevOutFetcher := txscript.NewCannedPrevOutputFetcher( + p.Inputs[0].WitnessUtxo.PkScript, int64(p.Inputs[0].WitnessUtxo.Value), + ) + sigHashes := txscript.NewTxSigHashes(p.UnsignedTx, prevOutFetcher) + hash, err := txscript.CalcWitnessSigHash(p.Inputs[0].WitnessScript, sigHashes, txscript.SigHashAll, p.UnsignedTx, 0, int64(p.Inputs[0].WitnessUtxo.Value)) + if err != nil { + return nil, err + } + result := hex.EncodeToString(hash) + return &result, nil +} \ No newline at end of file diff --git a/remotesigning/validator.go b/remotesigning/validator.go index 31d64c9..680d7f5 100644 --- a/remotesigning/validator.go +++ b/remotesigning/validator.go @@ -1,7 +1,11 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved package remotesigning -import "github.com/lightsparkdev/go-sdk/webhooks" +import ( + "strings" + + "github.com/lightsparkdev/go-sdk/webhooks" +) // Validator an interface which decides whether to sign or reject a remote signing webhook event. type Validator interface { @@ -13,3 +17,35 @@ type PositiveValidator struct{} func (v PositiveValidator) ShouldSign(webhooks.WebhookEvent) bool { return true } + +type HashValidator struct {} + +func (v HashValidator) ShouldSign(webhookEvent webhooks.WebhookEvent) bool { + request, err := ParseDeriveAndSignRequest(webhookEvent) + if err != nil { + // Only validate DeriveAndSignRequest events + return true + } + + for _, signing := range request.SigningJobs { + if strings.HasSuffix(signing.DerivationPath, "/2") { + msg, err := CalculateWitnessHashPSBT(*signing.Transaction) + if err != nil { + return false + } + if strings.Compare(*msg, signing.Message) != 0 { + return false + } + } else { + msg, err := CalculateWitnessHash(*signing.Amount, *signing.Script, *signing.Transaction) + if err != nil { + return false + } + if strings.Compare(*msg, signing.Message) != 0 { + return false + } + } + } + + return true +} \ No newline at end of file