From 03dd948951d29e3ce850694fad2bf9da0f397a44 Mon Sep 17 00:00:00 2001 From: mrz1836 Date: Wed, 28 Oct 2020 11:32:10 -0400 Subject: [PATCH] Minor fixes, clean up --- aip.go | 573 ++++++++++++++++++++++++++-------------------------- aip_test.go | 51 +++-- 2 files changed, 327 insertions(+), 297 deletions(-) diff --git a/aip.go b/aip.go index f62c3a5..23d11d6 100644 --- a/aip.go +++ b/aip.go @@ -1,283 +1,290 @@ -package aip - -import ( - "encoding/hex" - "fmt" - "log" - "strconv" - "strings" - - "github.com/bitcoinschema/go-bitcoin" - "github.com/rohenaz/go-bob" - "github.com/tonicpow/go-paymail" -) - -// Prefix is the Bitcom prefix used by AIP -const Prefix = "15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva" - -// Algorithm is an enum for the different possible signature algorithms -type Algorithm string - -// Algorithms -const ( - PAYMAIL Algorithm = "paymail" - BITCOIN_ECDSA Algorithm = "BITCOIN_ECDSA" -) - -// Aip is Author Identity Protocol -type Aip struct { - Algorithm Algorithm - Address string - Data []string - Signature string - Indicies []int `json:"indicies,omitempty" bson:"indicies,omitempty"` -} - -// New creates a new Aip struct -func New() *Aip { - return &Aip{} -} - -// SetData sets the data the AIP signature is signing -func (a *Aip) SetData(tapes []bob.Tape) { - - var data = []string{"j"} - - if len(a.Indicies) == 0 { - // walk over all output values and concatenate them until we hit the aip prefix, then add in the seperator - - for _, tape := range tapes { - for _, cell := range tape.Cell { - if cell.S != Prefix { - // Skip the OPS - if cell.Ops != "" { - continue - } - data = append(data, cell.S) - } else { - data = append(data, "|") - a.Data = data - return - } - } - } - - } else { - var indexCt = 0 - - for _, tape := range tapes { - for _, cell := range tape.Cell { - if cell.S != Prefix && contains(a.Indicies, indexCt) { - data = append(data, cell.S) - } else { - data = append(data, "|") - } - indexCt = indexCt + 1 - } - } - - a.Data = data - } -} - -func contains(s []int, e int) bool { - for _, a := range s { - if a == e { - return true - } - } - return false -} - -// FromTape takes a BOB Tape and returns a Aip data structure. Using from tape alone will prevent validation (data is needed via SetData to enable) -func (a *Aip) FromTape(tape bob.Tape) { - - // log.Println("Cell len is", len(tape.Cell)) - if len(tape.Cell) < 4 || tape.Cell[0].S != Prefix { - return - } - - a.Algorithm = Algorithm(tape.Cell[1].S) - a.Address = tape.Cell[2].S - a.Signature = tape.Cell[3].B // Is this B or S???? - - if len(tape.Cell) > 4 { - - a.Indicies = make([]int, len(tape.Cell)-4) - - // TODO: Consider OP_RETURN is included in sig when processing a tx using indices - // Loop over remaining indicies if they exist and append to indicies slice - for x := 4; x < len(tape.Cell); x++ { - // log.Println("X IS", x) - // log.Printf("Cell Data %+v", tape.Cell[x]) - index, _ := strconv.ParseUint(tape.Cell[x].S, 10, 64) - a.Indicies = append(a.Indicies, int(index)) - } - // log.Printf("THE IDXS %+v", a.Indicies) - } -} - -// SignOpReturnData appends a signature to a Bob Tx by adding a protocol separator pushdata followed by AIP information -func (a *Aip) SignOpReturnData(output bob.Output, algorithm Algorithm, addressString string, privKey string) (*bob.Output, error) { - - var dataToSign []string - for _, tape := range output.Tape { - for _, cell := range tape.Cell { - if len(cell.S) > 0 { - dataToSign = append(dataToSign, cell.S) - } else { - // TODO: Review this case. Should we assume the b64 is signed? Should protocol doc for AIP mention this? - dataToSign = append(dataToSign, cell.B) - } - } - } - // get data to sign from bob tx - // TODO: We need a live paymail set up with matching key in the test... somehow... - if algorithm == PAYMAIL { - // pubkey is used, dervice address - addr, err := bitcoin.GetAddressFromPubKeyString(addressString) - if err != nil { - return nil, err - } - addressString = addr.String() - } - - a.Sign(privKey, strings.Join(dataToSign, ""), algorithm, addressString) - a.Data = dataToSign - - output.Tape = append(output.Tape, bob.Tape{ - Cell: []bob.Cell{{ - H: hex.EncodeToString([]byte(Prefix)), - S: Prefix, - }, { - H: hex.EncodeToString([]byte(algorithm)), - S: string(algorithm), - }, { - H: hex.EncodeToString([]byte(addressString)), - S: addressString, - }, { - H: hex.EncodeToString([]byte(a.Signature)), - S: a.Signature, - }}, - }) - - return &output, nil -} - -// Sign will provide an AIP signature for a given private key and data. Just set paymail = "" when using BITCOIN_ECDSA signature -func (a *Aip) Sign(privKey string, message string, algorithm Algorithm, paymail string) (ok bool) { - - // pk = bsvec.PrivateKey - // pk.Sign(data) - switch algorithm { - case BITCOIN_ECDSA: - if paymail != "" { - // Error if paymail is provided, but algorithm is BITCOIN_ECDSA - return - } - sig, err := bitcoin.SignMessage(privKey, message) - if err != nil { - return - } - a.Signature = sig - address, err := bitcoin.GetAddressFromPrivateKey(privKey) - if err != nil { - return - } - a.Address = address - case PAYMAIL: - sig, err := bitcoin.SignMessage(privKey, message) - if err != nil { - return - } - a.Signature = sig - a.Address = paymail - } - a.Algorithm = algorithm - return true -} - -func getPki(paymailString string) (*paymail.PKI, error) { - // Load the client - client, err := paymail.NewClient(nil, nil) - if err != nil { - log.Fatalf("error loading client: %s", err.Error()) - } - - paymailParts := strings.Split(paymailString, "@") - - // Get the capabilities - // This is required first to get the corresponding PKI endpoint url - var capabilities *paymail.Capabilities - if capabilities, err = client.GetCapabilities(paymailParts[1], paymail.DefaultPort); err != nil { - log.Fatal("error getting capabilities: " + err.Error()) - } - log.Println("found capabilities: ", len(capabilities.Capabilities)) - - // Extract the PKI URL from the capabilities response - pkiURL := capabilities.GetString(paymail.BRFCPki, paymail.BRFCPkiAlternate) - - // Get the actual PKI - var pki *paymail.PKI - if pki, err = client.GetPKI(pkiURL, paymailParts[0], paymailParts[1]); err != nil { - log.Fatal("error getting pki: " + err.Error()) - } - log.Println("found pki:", pki) - return pki, nil -} - -// Validate returns true if the given AIP signature is valid for given data -func (a *Aip) Validate(paymailAddress string) (ok bool) { - if len(a.Data) == 0 { - return - } - switch a.Algorithm { - case BITCOIN_ECDSA: - // Validate verifies a Bitcoin signed message signature - err := bitcoin.VerifyMessage(a.Address, a.Signature, strings.Join(a.Data, "")) - return err == nil - case PAYMAIL: - if len(paymailAddress) == 0 { - log.Println("npo paymail address but using paymail algorithm") - return false - } - pki, err := getPki(paymailAddress) - if err != nil { - return false - } - // Get the public address for this paymail from pki - - addr, err := bitcoin.GetAddressFromPubKeyString(pki.PubKey) - if err != nil { - return false - } - - if paymailAddress != addr.String() { - fmt.Printf("Address mismatch %s", addr) - return false - } - - // You get the address associated with the pki instead of the current address - err = bitcoin.VerifyMessage(addr.String(), a.Signature, strings.Join(a.Data, "")) - return err == nil - } - return -} - -// ValidateTapes validates the AIP signature for a given []bob.Tape -func ValidateTapes(tapes []bob.Tape) bool { - - var aipTape bob.Tape - for _, tape := range tapes { - // Once we hit AIP Prefix, stop - if tape.Cell[0].S == Prefix { - aipTape = tape - break - } - } - - a := New() - a.FromTape(aipTape) - a.SetData(tapes) - return a.Validate("") -} +package aip + +import ( + "encoding/hex" + "log" + "net" + "strconv" + "strings" + + "github.com/bitcoinschema/go-bitcoin" + "github.com/bitcoinsv/bsvutil" + "github.com/rohenaz/go-bob" + "github.com/tonicpow/go-paymail" +) + +// Prefix is the Bitcom prefix used by AIP +const Prefix = "15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva" + +// Algorithm is an enum for the different possible signature algorithms +type Algorithm string + +// Algorithms +const ( + Paymail Algorithm = "paymail" + BitcoinECDSA Algorithm = "BITCOIN_ECDSA" +) + +// Aip is Author Identity Protocol +type Aip struct { + Algorithm Algorithm + Address string + Data []string + Signature string + Indices []int `json:"indices,omitempty" bson:"indices,omitempty"` +} + +// New creates a new Aip struct +func New() *Aip { + return &Aip{} +} + +// SetData sets the data the AIP signature is signing +func (a *Aip) SetData(tapes []bob.Tape) { + + var data = []string{"j"} + + if len(a.Indices) == 0 { + // walk over all output values and concatenate them until we hit the aip prefix, then add in the separator + + for _, tape := range tapes { + for _, cell := range tape.Cell { + if cell.S != Prefix { + // Skip the OPS + if cell.Ops != "" { + continue + } + data = append(data, cell.S) + } else { + data = append(data, "|") + a.Data = data + return + } + } + } + + } else { + var indexCt = 0 + + for _, tape := range tapes { + for _, cell := range tape.Cell { + if cell.S != Prefix && contains(a.Indices, indexCt) { + data = append(data, cell.S) + } else { + data = append(data, "|") + } + indexCt = indexCt + 1 + } + } + + a.Data = data + } +} + +func contains(s []int, e int) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +// FromTape takes a BOB Tape and returns a Aip data structure. +// Using from tape alone will prevent validation (data is needed via SetData to enable) +func (a *Aip) FromTape(tape bob.Tape) { + + // log.Println("Cell len is", len(tape.Cell)) + if len(tape.Cell) < 4 || tape.Cell[0].S != Prefix { + return + } + + a.Algorithm = Algorithm(tape.Cell[1].S) + a.Address = tape.Cell[2].S + a.Signature = tape.Cell[3].B // Is this B or S???? + + if len(tape.Cell) > 4 { + + a.Indices = make([]int, len(tape.Cell)-4) + + // TODO: Consider OP_RETURN is included in sig when processing a tx using indices + // Loop over remaining indices if they exist and append to indices slice + for x := 4; x < len(tape.Cell); x++ { + // log.Println("X IS", x) + // log.Printf("Cell Data %+v", tape.Cell[x]) + index, _ := strconv.ParseUint(tape.Cell[x].S, 10, 64) + a.Indices = append(a.Indices, int(index)) + } + } +} + +// SignOpReturnData appends a signature to a Bob Tx by adding a +// protocol separator push_data followed by AIP information +func (a *Aip) SignOpReturnData(output bob.Output, algorithm Algorithm, + addressString string, privKey string) (*bob.Output, error) { + + var dataToSign []string + for _, tape := range output.Tape { + for _, cell := range tape.Cell { + if len(cell.S) > 0 { + dataToSign = append(dataToSign, cell.S) + } else { + // TODO: Review this case. Should we assume the b64 is signed? + // Should protocol doc for AIP mention this? + dataToSign = append(dataToSign, cell.B) + } + } + } + // get data to sign from bob tx + // TODO: We need a live paymail set up with matching key in the test... somehow... + if algorithm == Paymail { + // pubkey is used, derive address + addr, err := bitcoin.GetAddressFromPubKeyString(addressString) + if err != nil { + return nil, err + } + addressString = addr.String() + } + + a.Sign(privKey, strings.Join(dataToSign, ""), algorithm, addressString) + a.Data = dataToSign + + output.Tape = append(output.Tape, bob.Tape{ + Cell: []bob.Cell{{ + H: hex.EncodeToString([]byte(Prefix)), + S: Prefix, + }, { + H: hex.EncodeToString([]byte(algorithm)), + S: string(algorithm), + }, { + H: hex.EncodeToString([]byte(addressString)), + S: addressString, + }, { + H: hex.EncodeToString([]byte(a.Signature)), + S: a.Signature, + }}, + }) + + return &output, nil +} + +// Sign will provide an AIP signature for a given private key and data. +// Just set paymail = "" when using BITCOIN_ECDSA signature +func (a *Aip) Sign(privKey string, message string, algorithm Algorithm, paymail string) (ok bool) { + + // pk = bsvec.PrivateKey + // pk.Sign(data) + switch algorithm { + case BitcoinECDSA: + if paymail != "" { + // Error if paymail is provided, but algorithm is BITCOIN_ECDSA + return + } + sig, err := bitcoin.SignMessage(privKey, message) + if err != nil { + return + } + a.Signature = sig + address, err := bitcoin.GetAddressFromPrivateKey(privKey) + if err != nil { + return + } + a.Address = address + case Paymail: + sig, err := bitcoin.SignMessage(privKey, message) + if err != nil { + return + } + a.Signature = sig + a.Address = paymail + } + a.Algorithm = algorithm + return true +} + +func getPki(paymailString string) (*paymail.PKI, error) { + + // Load the client + client, err := paymail.NewClient(nil, nil) + if err != nil { + return nil, err + } + + // Parse the paymail address + alias, domain, _ := paymail.SanitizePaymail(paymailString) + + var srv *net.SRV + srv, err = client.GetSRVRecord(paymail.DefaultServiceName, paymail.DefaultProtocol, domain) + if err != nil { + return nil, err + } + + // Get the capabilities + // This is required first to get the corresponding PKI endpoint url + var capabilities *paymail.Capabilities + if capabilities, err = client.GetCapabilities(srv.Target, paymail.DefaultPort); err != nil { + return nil, err + } + + // Extract the PKI URL from the capabilities response + pkiURL := capabilities.GetString(paymail.BRFCPki, paymail.BRFCPkiAlternate) + + // Get the actual PKI + return client.GetPKI(pkiURL, alias, domain) +} + +// Validate returns true if the given AIP signature is valid for given data +func (a *Aip) Validate(paymailAddress string) bool { + if len(a.Data) == 0 { + return false + } + switch a.Algorithm { + case BitcoinECDSA: + // Validate verifies a Bitcoin signed message signature + err := bitcoin.VerifyMessage(a.Address, a.Signature, strings.Join(a.Data, "")) + return err == nil + case Paymail: + if len(paymailAddress) == 0 { + log.Println("no paymail address but using paymail algorithm") + return false + } + pki, err := getPki(paymailAddress) + if err != nil { + return false + } + + // Get the public address for this paymail from pki + var addr *bsvutil.LegacyAddressPubKeyHash + if addr, err = bitcoin.GetAddressFromPubKeyString(pki.PubKey); err != nil { + return false + } + + if paymailAddress != addr.String() { + return false + } + + // You get the address associated with the pki instead of the current address + err = bitcoin.VerifyMessage(addr.String(), a.Signature, strings.Join(a.Data, "")) + return err == nil + default: + return false + } +} + +// ValidateTapes validates the AIP signature for a given []bob.Tape +func ValidateTapes(tapes []bob.Tape) bool { + + var aipTape bob.Tape + for _, tape := range tapes { + // Once we hit AIP Prefix, stop + if tape.Cell[0].S == Prefix { + aipTape = tape + break + } + } + + a := New() + a.FromTape(aipTape) + a.SetData(tapes) + return a.Validate("") +} diff --git a/aip_test.go b/aip_test.go index 2cbfb1a..323b1a5 100644 --- a/aip_test.go +++ b/aip_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/bitcoinschema/go-bitcoin" + "github.com/libsv/libsv/transaction" "github.com/rohenaz/go-bob" ) @@ -23,11 +24,14 @@ func TestValidate(t *testing.T) { const sampleBobTx = `{ "_id": "5f08ddb0f797435fbff1ddf0", "tx": { "h": "744a55a8637aa191aa058630da51803abbeadc2de3d65b4acace1f5f10789c5b" }, "out": [ { "i": 0, "tape": [ { "cell": [ { "op": 0, "ops": "OP_0", "i": 0, "ii": 0 }, { "op": 106, "ops": "OP_RETURN", "i": 1, "ii": 1 } ], "i": 0 }, { "cell": [ { "s": "1BAPSuaPnfGnSBM3GLV9yhxUdYe4vGbdMT", "h": "31424150537561506e66476e53424d33474c56397968785564596534764762644d54", "b": "MUJBUFN1YVBuZkduU0JNM0dMVjl5aHhVZFllNHZHYmRNVA==", "i": 0, "ii": 2 }, { "s": "ATTEST", "h": "415454455354", "b": "QVRURVNU", "i": 1, "ii": 3 }, { "s": "cf39fc55da24dc23eff1809e6e6cf32a0fe6aecc81296543e9ac84b8c501bac5", "h": "63663339666335356461323464633233656666313830396536653663663332613066653661656363383132393635343365396163383462386335303162616335", "b": "Y2YzOWZjNTVkYTI0ZGMyM2VmZjE4MDllNmU2Y2YzMmEwZmU2YWVjYzgxMjk2NTQzZTlhYzg0YjhjNTAxYmFjNQ==", "i": 2, "ii": 4 }, { "s": "0", "h": "30", "b": "MA==", "i": 3, "ii": 5 } ], "i": 1 }, { "cell": [ { "s": "15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva", "h": "313550636948473232534e4c514a584d6f5355615756693757537163376843667661", "b": "MTVQY2lIRzIyU05MUUpYTW9TVWFXVmk3V1NxYzdoQ2Z2YQ==", "i": 0, "ii": 7 }, { "s": "BITCOIN_ECDSA", "h": "424954434f494e5f4543445341", "b": "QklUQ09JTl9FQ0RTQQ==", "i": 1, "ii": 8 }, { "s": "134a6TXxzgQ9Az3w8BcvgdZyA5UqRL89da", "h": "31333461365458787a675139417a33773842637667645a7941355571524c38396461", "b": "MTM0YTZUWHh6Z1E5QXozdzhCY3ZnZFp5QTVVcVJMODlkYQ==", "i": 2, "ii": 9 }, { "s": "\u001f�nm�3坨\u001b�{\u001f\t��\u0000��(ӏ��h�D��o\u000b\u0006�$�(\u001a�'i��_�\u0006YA\"\f��ޚ` + "`" + `/U.\u0012�^W�\n", "h": "1fe96e6df733e59da81bc07b1f098ff19fad00b3fe28d38f81e768ed44d7c16f0b06932480281ab42769bdbb5fef065941220ccfcdde9a602f552e12dc5e57d70a", "b": "H+lubfcz5Z2oG8B7HwmP8Z+tALP+KNOPgedo7UTXwW8LBpMkgCgatCdpvbtf7wZZQSIMz83emmAvVS4S3F5X1wo=", "i": 3, "ii": 10 } ], "i": 2 } ], "e": { "v": 0, "i": 0, "a": "false" } }, { "i": 1, "tape": [ { "cell": [ { "op": 118, "ops": "OP_DUP", "i": 0, "ii": 0 }, { "op": 169, "ops": "OP_HASH160", "i": 1, "ii": 1 }, { "s": "�\no;L˺��E\t^��{i\u0011}", "h": "d27f0a6f3b4ccbbacaf945095ed3eeb97b69117d", "b": "0n8KbztMy7rK+UUJXtPuuXtpEX0=", "i": 2, "ii": 2 }, { "op": 136, "ops": "OP_EQUALVERIFY", "i": 3, "ii": 3 }, { "op": 172, "ops": "OP_CHECKSIG", "i": 4, "ii": 4 } ], "i": 0 } ], "e": { "v": 14492205, "i": 1, "a": "1LC16EQVsqVYGeYTCrjvNf8j28zr4DwBuk" } } ], "lock": 0, "timestamp": 1594416560292 }` bobData := bob.New() - bobData.FromString(sampleBobTx) + err := bobData.FromString(sampleBobTx) + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } tapes := bobData.Out[0].Tape if !ValidateTapes(tapes) { - t.Error("Failed to validate AIP signature") + t.Error("failed to validate AIP signature") } } @@ -36,14 +40,18 @@ func TestSetData(t *testing.T) { const sampleBobTx = `{ "_id": "5f08ddb0f797435fbff1ddf0", "tx": { "h": "744a55a8637aa191aa058630da51803abbeadc2de3d65b4acace1f5f10789c5b" }, "in": [ { "i": 0, "seq": 4294967295, "tape": [ { "cell": [ { "s": "0E\u0002!\u0000�\u0000��>�ȇ�ii}6��\\\t.���eB�\u0015\u0016�Ezd\u0002 !��6�V��L\u0002�-)�Ή=\f\u0003\u001co\u001f5|�\u001dn2��A", "h": "3045022100e000f9e33ebac8878269697d368edc5c092ee48be79ec965429e1516d1457a6402202195d036f456a1cf4c02e2aa2d29b5ce893d0c031c6f1f357ce91d6e32e3e8a541", "b": "MEUCIQDgAPnjPrrIh4JpaX02jtxcCS7ki+eeyWVCnhUW0UV6ZAIgIZXQNvRWoc9MAuKqLSm1zok9DAMcbx81fOkdbjLj6KVB", "i": 0, "ii": 0 }, { "s": "\u0004@��8��x��x���,#\u001d�(��B�A%\f����E��\u0000��T[�=(�\u0017Ϳ\u0001\u0010*\u001cr\\iZ��\u0007Ha�\u0018WM�(", "h": "0440ffb338848f78bfbb78b9b4a82c231dc728ceef42b341250c84ba99cf458bf2af0095df545bef3d28e717cdbf01102a1c725c695adfe40748619518574df228", "b": "BED/sziEj3i/u3i5tKgsIx3HKM7vQrNBJQyEupnPRYvyrwCV31Rb7z0o5xfNvwEQKhxyXGla3+QHSGGVGFdN8ig=", "i": 1, "ii": 1 } ], "i": 0 } ], "e": { "h": "eec0d4693a11b441211c046d25ce49514e72be0ce4437f3805af2d93ad905bc3", "i": 1, "a": "1LC16EQVsqVYGeYTCrjvNf8j28zr4DwBuk" } } ], "out": [ { "i": 0, "tape": [ { "cell": [ { "op": 0, "ops": "OP_0", "i": 0, "ii": 0 }, { "op": 106, "ops": "OP_RETURN", "i": 1, "ii": 1 } ], "i": 0 }, { "cell": [ { "s": "1BAPSuaPnfGnSBM3GLV9yhxUdYe4vGbdMT", "h": "31424150537561506e66476e53424d33474c56397968785564596534764762644d54", "b": "MUJBUFN1YVBuZkduU0JNM0dMVjl5aHhVZFllNHZHYmRNVA==", "i": 0, "ii": 2 }, { "s": "ATTEST", "h": "415454455354", "b": "QVRURVNU", "i": 1, "ii": 3 }, { "s": "cf39fc55da24dc23eff1809e6e6cf32a0fe6aecc81296543e9ac84b8c501bac5", "h": "63663339666335356461323464633233656666313830396536653663663332613066653661656363383132393635343365396163383462386335303162616335", "b": "Y2YzOWZjNTVkYTI0ZGMyM2VmZjE4MDllNmU2Y2YzMmEwZmU2YWVjYzgxMjk2NTQzZTlhYzg0YjhjNTAxYmFjNQ==", "i": 2, "ii": 4 }, { "s": "0", "h": "30", "b": "MA==", "i": 3, "ii": 5 } ], "i": 1 }, { "cell": [ { "s": "15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva", "h": "313550636948473232534e4c514a584d6f5355615756693757537163376843667661", "b": "MTVQY2lIRzIyU05MUUpYTW9TVWFXVmk3V1NxYzdoQ2Z2YQ==", "i": 0, "ii": 7 }, { "s": "BITCOIN_ECDSA", "h": "424954434f494e5f4543445341", "b": "QklUQ09JTl9FQ0RTQQ==", "i": 1, "ii": 8 }, { "s": "134a6TXxzgQ9Az3w8BcvgdZyA5UqRL89da", "h": "31333461365458787a675139417a33773842637667645a7941355571524c38396461", "b": "MTM0YTZUWHh6Z1E5QXozdzhCY3ZnZFp5QTVVcVJMODlkYQ==", "i": 2, "ii": 9 }, { "s": "\u001f�nm�3坨\u001b�{\u001f\t��\u0000��(ӏ��h�D��o\u000b\u0006�$�(\u001a�'i��_�\u0006YA\"\f��ޚ` + "`" + `/U.\u0012�^W�\n", "h": "1fe96e6df733e59da81bc07b1f098ff19fad00b3fe28d38f81e768ed44d7c16f0b06932480281ab42769bdbb5fef065941220ccfcdde9a602f552e12dc5e57d70a", "b": "H+lubfcz5Z2oG8B7HwmP8Z+tALP+KNOPgedo7UTXwW8LBpMkgCgatCdpvbtf7wZZQSIMz83emmAvVS4S3F5X1wo=", "i": 3, "ii": 10 } ], "i": 2 } ], "e": { "v": 0, "i": 0, "a": "false" } }, { "i": 1, "tape": [ { "cell": [ { "op": 118, "ops": "OP_DUP", "i": 0, "ii": 0 }, { "op": 169, "ops": "OP_HASH160", "i": 1, "ii": 1 }, { "s": "�\no;L˺��E\t^��{i\u0011}", "h": "d27f0a6f3b4ccbbacaf945095ed3eeb97b69117d", "b": "0n8KbztMy7rK+UUJXtPuuXtpEX0=", "i": 2, "ii": 2 }, { "op": 136, "ops": "OP_EQUALVERIFY", "i": 3, "ii": 3 }, { "op": 172, "ops": "OP_CHECKSIG", "i": 4, "ii": 4 } ], "i": 0 } ], "e": { "v": 14492205, "i": 1, "a": "1LC16EQVsqVYGeYTCrjvNf8j28zr4DwBuk" } } ], "lock": 0, "timestamp": 1594416560292 }` bobData := bob.New() - bobData.FromString(sampleBobTx) + err := bobData.FromString(sampleBobTx) + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } + aip := New() aip.SetData(bobData.Out[0].Tape) // 0x6a 1BAPSuaPnfGnSBM3GLV9yhxUdYe4vGbdMT ATTEST cf39fc55da24dc23eff1809e6e6cf32a0fe6aecc81296543e9ac84b8c501bac5 0 | // 0x6a (OP_RETURN) in ascii is 'j' if aip.Data[0] != "j" || aip.Data[1] != "1BAPSuaPnfGnSBM3GLV9yhxUdYe4vGbdMT" || aip.Data[2] != "ATTEST" && aip.Data[3] != "cf39fc55da24dc23eff1809e6e6cf32a0fe6aecc81296543e9ac84b8c501bac5" || aip.Data[4] != "0" { - t.Errorf("Failed setting aip data %+v", aip.Data) + t.Fatalf("failed setting aip data %+v", aip.Data) } } @@ -52,43 +60,58 @@ func TestSign(t *testing.T) { privateKey, err := bitcoin.PrivateKeyFromString(pk) if err != nil { - t.Errorf("Failed to get private key") + t.Fatalf("failed to get private key") } opReturn1 := bitcoin.OpReturnData{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}} - tx, err := bitcoin.CreateTx(nil, nil, []bitcoin.OpReturnData{opReturn1}, privateKey) + + var tx *transaction.Transaction + tx, err = bitcoin.CreateTx(nil, nil, []bitcoin.OpReturnData{opReturn1}, privateKey) if err != nil { - t.Errorf("Failed to create tx %s", err) + t.Fatalf("failed to create tx %s", err) } // TODO // Get a bob bobTx := bob.New() - bobTx.FromRawTxString(tx.ToString()) + err = bobTx.FromRawTxString(tx.ToString()) + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } // if !aipTx.Validate() { // t.Errorf("could not validate paymail signature") // } } -//TODO: This test does not pass yet. Need to figure out how to validate paymail signatures +// TODO: This test does not pass yet. Need to figure out how to validate paymail signatures func TestSignOpReturnData(t *testing.T) { pk := "80699541455b59a8a8a33b85892319de8b8e8944eb8b48e9467137825ae192e59f01" privateKey, publicKey, err := bitcoin.PrivateAndPublicKeys(pk) if err != nil { - t.Errorf("Failed to get private key") + t.Fatalf("failed to get private key") } opReturn1 := bitcoin.OpReturnData{[]byte("prefix1"), []byte("example data"), []byte{0x13, 0x37}} - tx, err := bitcoin.CreateTx(nil, nil, []bitcoin.OpReturnData{opReturn1}, privateKey) + + var tx *transaction.Transaction + tx, err = bitcoin.CreateTx(nil, nil, []bitcoin.OpReturnData{opReturn1}, privateKey) if err != nil { - t.Errorf("Failed to create tx %s", err) + t.Fatalf("failed to create tx %s", err) } // Get a bob bobTx := bob.New() - bobTx.FromRawTxString(tx.ToString()) + err = bobTx.FromRawTxString(tx.ToString()) + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } aipTx := New() - signedOutput, err := aipTx.SignOpReturnData(bobTx.Out[0], PAYMAIL, hex.EncodeToString(publicKey.SerializeCompressed()), pk) + + var signedOutput *bob.Output + signedOutput, err = aipTx.SignOpReturnData(bobTx.Out[0], Paymail, hex.EncodeToString(publicKey.SerializeCompressed()), pk) + if err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } bobTx.Out = append(bobTx.Out, *signedOutput)