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

Tx json #23

Merged
merged 13 commits into from
Jun 1, 2021
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ linters:
- gocritic # use this for very opinionated linting
- gochecknoglobals
- whitespace
- gci
- wsl
- goerr113
- godot
Expand Down
57 changes: 51 additions & 6 deletions bscript/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ var (
ErrNotP2PKH = errors.New("not a P2PKH")
)

// ScriptKey types.
const (
ScriptTypePubKey = "pubkey"
ScriptTypePubKeyHash = "pubkeyhash"
ScriptTypeNonStandard = "nonstandard"
ScriptTypeMultiSig = "multisig"
ScriptTypeNullData = "nulldata"
)

// Script type
type Script []byte

Expand Down Expand Up @@ -87,7 +96,7 @@ func NewP2PKHFromPubKeyHash(pubKeyHash []byte) (*Script, error) {
b := []byte{
OpDUP,
OpHASH160,
0x14,
OpDATA20,
}
b = append(b, pubKeyHash...)
b = append(b, OpEQUALVERIFY)
Expand Down Expand Up @@ -223,7 +232,7 @@ func (s *Script) IsP2PKH() bool {
return len(b) == 25 &&
b[0] == OpDUP &&
b[1] == OpHASH160 &&
b[2] == 0x14 &&
b[2] == OpDATA20 &&
b[23] == OpEQUALVERIFY &&
b[24] == OpCHECKSIG
}
Expand Down Expand Up @@ -255,7 +264,7 @@ func (s *Script) IsP2SH() bool {

return len(b) == 23 &&
b[0] == OpHASH160 &&
b[1] == 0x14 &&
b[1] == OpDATA20 &&
b[22] == OpEQUAL
}

Expand All @@ -264,8 +273,8 @@ func (s *Script) IsP2SH() bool {
func (s *Script) IsData() bool {
b := []byte(*s)

return b[0] == 0x6a ||
b[0] == 0x00 && b[1] == 0x6a
return b[0] == OpRETURN ||
b[0] == OpFALSE && b[1] == OpRETURN
}

// IsMultiSigOut returns true if this is a multisig output script.
Expand Down Expand Up @@ -303,7 +312,7 @@ func (s *Script) PublicKeyHash() ([]byte, error) {
return nil, ErrEmptyScript
}

if (*s)[0] != 0x76 || (*s)[1] != 0xa9 {
if (*s)[0] != OpDUP || (*s)[1] != OpHASH160 {
return nil, ErrNotP2PKH
}

Expand All @@ -315,6 +324,42 @@ func (s *Script) PublicKeyHash() ([]byte, error) {
return parts[0], nil
}

// ScriptType returns the type of script this is as a string.
func (s *Script) ScriptType() string {
theflyingcodr marked this conversation as resolved.
Show resolved Hide resolved
if s.IsP2PKH() {
return ScriptTypePubKeyHash
}
if s.IsP2PK() {
return ScriptTypePubKey
}
if s.IsMultiSigOut() {
return ScriptTypeMultiSig
}
if s.IsData() {
return ScriptTypeNullData
}
return ScriptTypeNonStandard
}

// Addresses will return all addresses found in the script, if any.
func (s *Script) Addresses() ([]string, error) {
addresses := make([]string, 0)
if s.IsP2PKH() {
pkh, err := s.PublicKeyHash()
if err != nil {
return nil, err
}
a, err := NewAddressFromPublicKeyHash(pkh, true)
if err != nil {
return nil, err
}
addresses = []string{a.AddressString}
}
// TODO: handle multisig, and other outputs
// https://github.com/libsv/go-bt/issues/6
return addresses, 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)
Expand Down
65 changes: 65 additions & 0 deletions input.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bt

import (
"encoding/hex"
"encoding/json"
"fmt"

"github.com/libsv/go-bt/bscript"
Expand Down Expand Up @@ -35,6 +36,70 @@ type Input struct {
SequenceNumber uint32
}

// inputJSON is used to covnert an input to and from json.
// Script is duplicated as we have our own name for unlockingScript
// but want to be compatible with node json also.
type inputJSON struct {
UnlockingScript *struct {
Asm string `json:"asm"`
Hex string `json:"hex"`
} `json:"unlockingScript,omitempty"`
ScriptSig *struct {
Asm string `json:"asm"`
Hex string `json:"hex"`
} `json:"scriptSig,omitempty"`
TxID string `json:"txid"`
Vout uint32 `json:"vout"`
Sequence uint32 `json:"sequence"`
}

// MarshalJSON will convert an input to json, expanding upon the
// input struct to add additional fields.
func (i *Input) MarshalJSON() ([]byte, error) {
asm, err := i.UnlockingScript.ToASM()
if err != nil {
return nil, err
}
input := &inputJSON{
TxID: hex.EncodeToString(i.PreviousTxIDBytes),
Vout: i.PreviousTxOutIndex,
UnlockingScript: &struct {
Asm string `json:"asm"`
Hex string `json:"hex"`
}{
Asm: asm,
Hex: i.UnlockingScript.String(),
},
Sequence: i.SequenceNumber,
}
return json.Marshal(input)
}

// UnmarshalJSON will convert a JSON input to an input.
func (i *Input) UnmarshalJSON(b []byte) error {
var ij inputJSON
if err := json.Unmarshal(b, &ij); err != nil {
return err
}
ptxID, err := hex.DecodeString(ij.TxID)
if err != nil {
return err
}
sig := ij.UnlockingScript
if sig == nil {
sig = ij.ScriptSig
}
s, err := bscript.NewFromHexString(sig.Hex)
if err != nil {
return err
}
i.UnlockingScript = s
i.PreviousTxIDBytes = ptxID
i.PreviousTxOutIndex = ij.Vout
i.SequenceNumber = ij.Sequence
return nil
}

// PreviousTxIDStr returns the Previous TxID as a hex string.
func (i *Input) PreviousTxIDStr() string {
return hex.EncodeToString(i.PreviousTxIDBytes)
Expand Down
74 changes: 74 additions & 0 deletions output.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bt
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"

"github.com/libsv/go-bt/bscript"
Expand All @@ -24,6 +25,79 @@ Txout-script / scriptPubKey Script <out-s
type Output struct {
Satoshis uint64
LockingScript *bscript.Script
index int
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@theflyingcodr yo i just realised this, don't think this should be in here... do you?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was using this to track the vout, I'll need to double the code again as it's been a while!

}

type outputJSON struct {
Value float64 `json:"value"`
Satoshis uint64 `json:"satoshis"`
Index int `json:"n"`
ScriptPubKey *struct {
Asm string `json:"asm"`
Hex string `json:"hex"`
ReqSigs int `json:"reqSigs,omitempty"`
Type string `json:"type"`
} `json:"scriptPubKey,omitempty"`
LockingScript *struct {
Asm string `json:"asm"`
Hex string `json:"hex"`
ReqSigs int `json:"reqSigs,omitempty"`
Type string `json:"type"`
} `json:"lockingScript,omitempty"`
}

// MarshalJSON will serialise an output to json.
func (o *Output) MarshalJSON() ([]byte, error) {
asm, err := o.LockingScript.ToASM()
if err != nil {
return nil, err
}
addresses, err := o.LockingScript.Addresses()
if err != nil {
return nil, err
}

output := &outputJSON{
Value: float64(o.Satoshis) / 100000000,
Satoshis: o.Satoshis,
Index: o.index,
LockingScript: &struct {
Asm string `json:"asm"`
Hex string `json:"hex"`
ReqSigs int `json:"reqSigs,omitempty"`
Type string `json:"type"`
}{
Asm: asm,
Hex: o.LockingScriptHexString(),
ReqSigs: len(addresses),
Type: o.LockingScript.ScriptType(),
},
}
return json.Marshal(output)
}

// UnmarshalJSON will convert a json serialised output to a bt Output.
func (o *Output) UnmarshalJSON(b []byte) error {
var oj outputJSON
if err := json.Unmarshal(b, &oj); err != nil {
return err
}
script := oj.LockingScript
if script == nil {
script = oj.ScriptPubKey
}
s, err := bscript.NewFromHexString(script.Hex)
if err != nil {
return err
}
if oj.Satoshis > 0 {
o.Satoshis = oj.Satoshis
} else {
o.Satoshis = uint64(oj.Value * 100000000)
}
o.index = oj.Index
o.LockingScript = s
return nil
}

// LockingScriptHexString returns the locking script
Expand Down
65 changes: 61 additions & 4 deletions tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"

"github.com/libsv/go-bk/crypto"
Expand Down Expand Up @@ -36,10 +38,64 @@ lock_time if non-zero and sequence numbers are < 0xFFFFFFFF: block height
// DO NOT CHANGE ORDER - Optimised memory via malign
//
type Tx struct {
Inputs []*Input
Outputs []*Output
Version uint32
LockTime uint32
Inputs []*Input `json:"vin"`
Outputs []*Output `json:"vout"`
Version uint32 `json:"version"`
LockTime uint32 `json:"locktime"`
}

type txJSON struct {
Version uint32 `json:"version"`
LockTime uint32 `json:"locktime"`
TxID string `json:"txid"`
Hash string `json:"hash"`
Size int `json:"size"`
Hex string `json:"hex"`
Inputs []*Input `json:"vin"`
Outputs []*Output `json:"vout"`
}

// MarshalJSON will serialise a transaction to json.
func (tx *Tx) MarshalJSON() ([]byte, error) {
if tx == nil {
return nil, errors.New("tx is nil so cannot be marshalled")
}
for i, o := range tx.Outputs {
o.index = i
}
txj := txJSON{
Version: tx.Version,
LockTime: tx.LockTime,
Inputs: tx.Inputs,
Outputs: tx.Outputs,
TxID: tx.TxID(),
Hash: tx.TxID(),
Size: len(tx.ToBytes()),
Hex: tx.String(),
}
return json.Marshal(txj)
}

// UnmarshalJSON will unmarshall a transaction that has been marshalled with this library.
func (tx *Tx) UnmarshalJSON(b []byte) error {
var txj txJSON
if err := json.Unmarshal(b, &txj); err != nil {
return err
}
// quick convert
if txj.Hex != "" {
t, err := NewTxFromString(txj.Hex)
if err != nil {
return err
}
*tx = *t
return nil
}
tx.Inputs = txj.Inputs
tx.Outputs = txj.Outputs
tx.LockTime = txj.LockTime
tx.Version = txj.Version
return nil
}

// NewTx creates a new transaction object with default values.
Expand Down Expand Up @@ -115,6 +171,7 @@ func NewTxFromStream(b []byte) (*Tx, int, error) {
if err != nil {
return nil, 0, err
}
output.index = int(i)
theflyingcodr marked this conversation as resolved.
Show resolved Hide resolved
offset += size
t.Outputs = append(t.Outputs, output)
}
Expand Down
Loading