generated from mrz1836/go-template
-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #595 from bitcoin-sv/feat-spv-790-Create-outputs-t…
…emplate-and-store-evaluated-template-in-destination feat(spv-790) Create outputs template and store evaluated template in destination
- Loading branch information
Showing
10 changed files
with
828 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
|
||
"github.com/libsv/go-bk/bec" | ||
|
||
"github.com/bitcoin-sv/spv-wallet/engine/pike" | ||
"github.com/bitcoin-sv/spv-wallet/engine/script/template" | ||
) | ||
|
||
func main() { | ||
// Example sender's public key (replace with actual sender's public key) | ||
// generating keys | ||
senderPublicKeyHex := "034252e5359a1de3b8ec08e6c29b80594e88fb47e6ae9ce65ee5a94f0d371d2cde" | ||
senderPublicKeyBytes, err := hex.DecodeString(senderPublicKeyHex) | ||
if err != nil { | ||
panic(err) | ||
} | ||
senderPubKey, err := bec.ParsePubKey(senderPublicKeyBytes, bec.S256()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
receiverPublicKeyHex := "027c1404c3ecb034053e6dd90bc68f7933284559c7d0763367584195a8796d9b0e" | ||
receiverPublicKeyBytes, err := hex.DecodeString(receiverPublicKeyHex) | ||
if err != nil { | ||
panic(err) | ||
} | ||
receiverPubKey, err := bec.ParsePubKey(receiverPublicKeyBytes, bec.S256()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// example of usage pike.GenerateOutputsTemplate | ||
outputsTemplate, _ := pike.GenerateOutputsTemplate(10000) | ||
fmt.Println(formatOutputs(outputsTemplate)) | ||
|
||
lockingScripts, err := pike.GenerateLockingScriptsFromTemplates(outputsTemplate, senderPubKey, receiverPubKey, "reference") | ||
if err != nil { | ||
fmt.Println("Error:", err) | ||
return | ||
} | ||
|
||
for _, script := range lockingScripts { | ||
fmt.Println("Locking Script:", script) | ||
} | ||
|
||
} | ||
|
||
// Helper function to format the outputs into a string | ||
func formatOutputs(outputs []*template.OutputTemplate) string { | ||
var result string | ||
for i, output := range outputs { | ||
result += fmt.Sprintf("Output %d: %v\n", i+1, output) | ||
} | ||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package pike_test | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
|
||
"github.com/libsv/go-bk/bec" | ||
|
||
"github.com/bitcoin-sv/spv-wallet/engine/pike" | ||
) | ||
|
||
func Example_generateLockingScripts() { | ||
// Example sender's public key (replace with actual sender's public key) | ||
senderPublicKeyHex := "034252e5359a1de3b8ec08e6c29b80594e88fb47e6ae9ce65ee5a94f0d371d2cde" | ||
senderPublicKeyBytes, err := hex.DecodeString(senderPublicKeyHex) | ||
if err != nil { | ||
panic(err) | ||
} | ||
senderPubKey, err := bec.ParsePubKey(senderPublicKeyBytes, bec.S256()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
receiverPublicKeyHex := "027c1404c3ecb034053e6dd90bc68f7933284559c7d0763367584195a8796d9b0e" | ||
receiverPublicKeyBytes, err := hex.DecodeString(receiverPublicKeyHex) | ||
if err != nil { | ||
panic(err) | ||
} | ||
receiverPubKey, err := bec.ParsePubKey(receiverPublicKeyBytes, bec.S256()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// Example usage of GenerateOutputsTemplate | ||
outputsTemplate, err := pike.GenerateOutputsTemplate(10000) | ||
if err != nil { | ||
panic(fmt.Errorf("Error generating outputs template - %w", err)) | ||
} | ||
|
||
// Example usage of GenerateLockingScriptsFromTemplates | ||
lockingScripts, err := pike.GenerateLockingScriptsFromTemplates(outputsTemplate, senderPubKey, receiverPubKey, "reference") | ||
if err != nil { | ||
panic(fmt.Errorf("Error generating locking scripts - %w", err)) | ||
} | ||
|
||
for _, script := range lockingScripts { | ||
fmt.Println("Locking Script:", script) | ||
} | ||
|
||
// Output: | ||
// Locking Script: 76a9147327490be831259f38b0f9ab019413e51d1b40c688ac | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Package pike provides functionality to work with Pay-to-PubKey-Hash (P2PKH) scripts in a blockchain context. | ||
// | ||
// P2PKH is a common type of Bitcoin transaction that locks funds to a specific public key hash, requiring the | ||
// corresponding private key to produce a valid signature for spending the funds. This package offers utilities | ||
// to create, decode, and validate P2PKH scripts. | ||
// | ||
// The package includes: | ||
// - Functions to generate P2PKH addresses from public keys. | ||
// - Methods to construct P2PKH scriptPubKey and scriptSig scripts. | ||
// - Utilities to decode and inspect P2PKH scripts and addresses. | ||
// - Validation functions to ensure the integrity and correctness of P2PKH scripts. | ||
// | ||
// This package is intended for developers working with Bitcoin or other cryptocurrencies that support P2PKH transactions. | ||
// It abstracts the low-level details and provides a high-level interface for creating and handling P2PKH scripts. | ||
package pike | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/libsv/go-bk/bec" | ||
"github.com/libsv/go-bt/v2/bscript" | ||
|
||
"github.com/bitcoin-sv/spv-wallet/engine/script/template" | ||
"github.com/bitcoin-sv/spv-wallet/engine/types/type42" | ||
) | ||
|
||
// GenerateOutputsTemplate creates a Pike output template | ||
func GenerateOutputsTemplate(satoshis uint64) ([]*template.OutputTemplate, error) { | ||
p2pkhTemplate, err := template.P2PKH(satoshis) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return []*template.OutputTemplate{p2pkhTemplate}, nil | ||
} | ||
|
||
// GenerateLockingScriptsFromTemplates converts Pike outputs templates to scripts | ||
func GenerateLockingScriptsFromTemplates(outputsTemplate []*template.OutputTemplate, senderPubKey, receiverPubKey *bec.PublicKey, reference string) ([]string, error) { | ||
lockingScripts := make([]string, len(outputsTemplate)) | ||
|
||
for idx, output := range outputsTemplate { | ||
templateScript, err := bscript.NewFromHexString(output.Script) | ||
if err != nil { | ||
return nil, fmt.Errorf("error creating script from hex string - %w", err) | ||
} | ||
|
||
dPK, err := type42.DeriveLinkedKey(senderPubKey, receiverPubKey, fmt.Sprintf("%s-%d", reference, idx)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
scriptBytes, err := template.Evaluate(*templateScript, dPK) | ||
if err != nil { | ||
return nil, fmt.Errorf("error evaluating template script - %w", err) | ||
} | ||
|
||
finalScript := bscript.Script(scriptBytes) | ||
|
||
lockingScripts[idx] = finalScript.String() | ||
} | ||
|
||
return lockingScripts, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package pike | ||
|
||
import ( | ||
"encoding/hex" | ||
"testing" | ||
|
||
"github.com/libsv/go-bk/bec" | ||
assert "github.com/stretchr/testify/require" | ||
|
||
"github.com/bitcoin-sv/spv-wallet/engine/script/template" | ||
) | ||
|
||
func TestGenerateLockingScriptsFromTemplates(t *testing.T) { | ||
// Define sample data | ||
senderPubKeyHex := "027c1404c3ecb034053e6dd90bc68f7933284559c7d0763367584195a8796d9b0e" | ||
senderPubKeyBytes, err := hex.DecodeString(senderPubKeyHex) | ||
assert.NoError(t, err) | ||
senderPubKey, err := bec.ParsePubKey(senderPubKeyBytes, bec.S256()) | ||
assert.NoError(t, err) | ||
|
||
receiverPubKeyHex := "03a34e456deecb6e6e9237e63e5b7d045d1d2a456eb6be43de1ec4e9ac9a07b50d" | ||
receiverPubKeyBytes, err := hex.DecodeString(receiverPubKeyHex) | ||
assert.NoError(t, err) | ||
receiverPubKey, err := bec.ParsePubKey(receiverPubKeyBytes, bec.S256()) | ||
assert.NoError(t, err) | ||
|
||
outputsTemplate := []*template.OutputTemplate{ | ||
{Script: "76a914000000000000000000000000000000000000000088ac"}, | ||
{Script: "76a914111111111111111111111111111111111111111188ac"}, | ||
} | ||
|
||
t.Run("Valid Case", func(t *testing.T) { | ||
lockingScripts, err := GenerateLockingScriptsFromTemplates(outputsTemplate, senderPubKey, receiverPubKey, "test-reference") | ||
assert.NoError(t, err) | ||
assert.Len(t, lockingScripts, len(outputsTemplate)) | ||
assert.Equal(t, outputsTemplate[0].Script, lockingScripts[0]) | ||
assert.Equal(t, outputsTemplate[1].Script, lockingScripts[1]) | ||
}) | ||
|
||
t.Run("Invalid Template Script", func(t *testing.T) { | ||
invalidTemplate := []*template.OutputTemplate{ | ||
{Script: "invalid-hex-string"}, // Invalid hex string | ||
} | ||
lockingScripts, err := GenerateLockingScriptsFromTemplates(invalidTemplate, senderPubKey, receiverPubKey, "test-reference") | ||
assert.Error(t, err) | ||
assert.Nil(t, lockingScripts) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package template | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/libsv/go-bk/bec" | ||
"github.com/libsv/go-bk/crypto" | ||
"github.com/libsv/go-bt/v2/bscript" | ||
"github.com/libsv/go-bt/v2/bscript/interpreter" | ||
) | ||
|
||
// Evaluate processes a given Bitcoin script by parsing it, replacing certain opcodes | ||
// with the public key hash, and returning the resulting script as a byte array. | ||
// Will replace any OP_PUBKEYHASH or OP_PUBKEY | ||
// | ||
// Parameters: | ||
// - script: A byte array representing the input script. | ||
// - pubKey: A pointer to a bec.PublicKey which provides the dedicated public key to be used in the evaluation. | ||
// | ||
// Returns: | ||
// - A byte array representing the evaluated script, or nil if an error occurs. | ||
func Evaluate(script []byte, pubKey *bec.PublicKey) ([]byte, error) { | ||
s := bscript.Script(script) | ||
|
||
parser := interpreter.DefaultOpcodeParser{} | ||
parsedScript, err := parser.Parse(&s) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Validate parsed opcodes | ||
for _, op := range parsedScript { | ||
if op.Value() == 0xFF { | ||
return nil, errors.New("invalid opcode") | ||
} | ||
} | ||
|
||
// Serialize the public key to compressed format | ||
dPKBytes := pubKey.SerialiseCompressed() | ||
|
||
// Apply Hash160 (SHA-256 followed by RIPEMD-160) to the compressed public key | ||
dPKHash := crypto.Hash160(dPKBytes) | ||
|
||
// Create a new script with the public key hash | ||
newScript := new(bscript.Script) | ||
if err := newScript.AppendPushData(dPKHash); err != nil { | ||
return nil, err | ||
} | ||
|
||
// Parse the public key hash script | ||
pkhParsed, err := parser.Parse(newScript) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Replace OP_PUBKEYHASH with the actual public key hash | ||
evaluated := make([]interpreter.ParsedOpcode, 0, len(parsedScript)) | ||
for _, op := range parsedScript { | ||
switch op.Value() { | ||
case bscript.OpPUBKEYHASH: | ||
evaluated = append(evaluated, pkhParsed...) | ||
case bscript.OpPUBKEY: | ||
return nil, errors.New("OP_PUBKEY not supported yet") | ||
default: | ||
evaluated = append(evaluated, op) | ||
} | ||
} | ||
|
||
// Unparse the evaluated opcodes back into a script | ||
finalScript, err := parser.Unparse(evaluated) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Cast *bscript.Script back to []byte | ||
return []byte(*finalScript), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Package template provides a collection of functions and types for working with script templates. | ||
package template | ||
|
||
import ( | ||
"encoding/hex" | ||
"errors" | ||
"sync" | ||
|
||
"github.com/libsv/go-bt/v2/bscript" | ||
) | ||
|
||
var ( | ||
scriptHex string | ||
once sync.Once | ||
) | ||
|
||
func initScriptHex() { | ||
opcodes := []byte{ | ||
bscript.OpDUP, | ||
bscript.OpHASH160, | ||
bscript.OpPUBKEYHASH, | ||
bscript.OpEQUALVERIFY, | ||
bscript.OpCHECKSIG, | ||
} | ||
|
||
// Convert opcodes to hexadecimal string | ||
scriptHex = hex.EncodeToString(opcodes) | ||
} | ||
|
||
// OutputTemplate represents the script and satoshis for a Pike output | ||
type OutputTemplate struct { | ||
Script string `json:"script"` | ||
Satoshis uint64 `json:"satoshis"` | ||
} | ||
|
||
// P2PKH creates a single output with the PIKE template | ||
func P2PKH(satoshis uint64) (*OutputTemplate, error) { | ||
|
||
if satoshis == 0 { | ||
return nil, errors.New("satoshis cannot be zero") | ||
} | ||
if satoshis == ^uint64(0) { | ||
return nil, errors.New("invalid satoshis") | ||
} | ||
|
||
// Initialize the scriptHex once | ||
once.Do(initScriptHex) | ||
|
||
// Create and return the PikeOutputsTemplate | ||
return &OutputTemplate{ | ||
Script: scriptHex, | ||
Satoshis: satoshis, | ||
}, nil | ||
} |
Oops, something went wrong.