From e1c34659f09c6992025f0bffc240505b208237ab Mon Sep 17 00:00:00 2001 From: mrz1836 Date: Wed, 28 Oct 2020 22:51:25 -0400 Subject: [PATCH] Added bob tests --- bob.go | 136 ++++++++++++++++++++++++++---------------------- bob_test.go | 146 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 208 insertions(+), 74 deletions(-) diff --git a/bob.go b/bob.go index c8e481f..cfaeba9 100644 --- a/bob.go +++ b/bob.go @@ -16,63 +16,6 @@ func NewFromTape(tape bob.Tape) (a *Aip) { return } -// SignBobOpReturnData appends a signature to a BOB Tx by adding a -// protocol separator followed by AIP information -func SignBobOpReturnData(privateKey string, algorithm Algorithm, output bob.Output) (*bob.Output, *Aip, error) { - - // Parse the data to sign - 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) - } - } - } - - // Sign the data - a, err := Sign(privateKey, algorithm, strings.Join(dataToSign, "")) - if err != nil { - return nil, nil, err - } - - // Create the output tape - 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(a.AlgorithmSigningComponent)), - S: a.AlgorithmSigningComponent, - }, { - H: hex.EncodeToString([]byte(a.Signature)), - S: a.Signature, - }}, - }) - - return &output, a, nil -} - -// ValidateTapes validates the AIP signature for a given []bob.Tape -func ValidateTapes(tapes []bob.Tape) bool { - for _, tape := range tapes { - // Once we hit AIP Prefix, stop - if tape.Cell[0].S == Prefix { - a := NewFromTape(tape) - a.SetDataFromTape(tapes) - return a.Validate() - } - } - return false -} - // FromTape takes a BOB Tape and returns a Aip data structure. // Using the FromTape() alone will prevent validation (data is needed via SetData to enable) func (a *Aip) FromTape(tape bob.Tape) { @@ -85,7 +28,7 @@ func (a *Aip) FromTape(tape bob.Tape) { // Set the AIP fields a.Algorithm = Algorithm(tape.Cell[1].S) a.AlgorithmSigningComponent = tape.Cell[2].S - a.Signature = tape.Cell[3].B // Is this B or S???? + a.Signature = tape.Cell[3].B // Store the indices if len(tape.Cell) > 4 { @@ -102,16 +45,28 @@ func (a *Aip) FromTape(tape bob.Tape) { } } -// todo: FromTapes() - looks through tapes trying to detect AIP +// NewFromTapes will create a new AIP object from a []bob.Tape +// Using the FromTapes() alone will prevent validation (data is needed via SetData to enable) +func NewFromTapes(tapes []bob.Tape) (a *Aip) { + for _, t := range tapes { + if len(t.Cell) > 0 && t.Cell[0].S == Prefix { + a = new(Aip) + a.FromTape(t) + a.SetDataFromTapes(tapes) + return + } + } + return +} -// SetDataFromTape sets the data the AIP signature is signing -func (a *Aip) SetDataFromTape(tapes []bob.Tape) { +// SetDataFromTapes sets the data the AIP signature is signing +func (a *Aip) SetDataFromTapes(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 + // 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 { @@ -146,6 +101,63 @@ func (a *Aip) SetDataFromTape(tapes []bob.Tape) { } } +// SignBobOpReturnData appends a signature to a BOB Tx by adding a +// protocol separator followed by AIP information +func SignBobOpReturnData(privateKey string, algorithm Algorithm, output bob.Output) (*bob.Output, *Aip, error) { + + // Parse the data to sign + 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) + } + } + } + + // Sign the data + a, err := Sign(privateKey, algorithm, strings.Join(dataToSign, "")) + if err != nil { + return nil, nil, err + } + + // Create the output tape + 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(a.AlgorithmSigningComponent)), + S: a.AlgorithmSigningComponent, + }, { + H: hex.EncodeToString([]byte(a.Signature)), + S: a.Signature, + }}, + }) + + return &output, a, nil +} + +// ValidateTapes validates the AIP signature for a given []bob.Tape +func ValidateTapes(tapes []bob.Tape) bool { + for _, tape := range tapes { + // Once we hit AIP Prefix, stop + if tape.Cell[0].S == Prefix { + a := NewFromTape(tape) + a.SetDataFromTapes(tapes) + return a.Validate() + } + } + return false +} + // contains looks in a slice for a given value func contains(s []int, e int) bool { for _, a := range s { diff --git a/bob_test.go b/bob_test.go index ad4fe8d..7edb9c1 100644 --- a/bob_test.go +++ b/bob_test.go @@ -1,12 +1,13 @@ package aip import ( + "fmt" "testing" "github.com/bitcoinschema/go-bob" ) -// TestSign will test the method Sign() +// TestNewFromTape will test the method NewFromTape() func TestNewFromTape(t *testing.T) { t.Parallel() @@ -15,8 +16,8 @@ func TestNewFromTape(t *testing.T) { if err != nil { t.Fatalf("error occurred: %s", err.Error()) } - bobInvalidData, err := bob.NewFromString(sampleInvalidBobTx) - if err != nil { + var bobInvalidData *bob.Tx + if bobInvalidData, err = bob.NewFromString(sampleInvalidBobTx); err != nil { t.Fatalf("error occurred: %s", err.Error()) } @@ -84,22 +85,143 @@ func TestNewFromTape(t *testing.T) { } } -func TestSetData(t *testing.T) { +// ExampleNewFromTape example using NewFromTape() +func ExampleNewFromTape() { + // Get BOB data from a TX + bobValidData, err := bob.NewFromString(sampleValidBobTx) + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } + + // Get from tape given the AIP index + a := NewFromTape(bobValidData.Out[0].Tape[2]) + + fmt.Printf("address: %s signature: %s", a.AlgorithmSigningComponent, a.Signature) + // Output:address: 134a6TXxzgQ9Az3w8BcvgdZyA5UqRL89da signature: H+lubfcz5Z2oG8B7HwmP8Z+tALP+KNOPgedo7UTXwW8LBpMkgCgatCdpvbtf7wZZQSIMz83emmAvVS4S3F5X1wo= +} + +// BenchmarkNewFromTape benchmarks the method NewFromTape() +func BenchmarkNewFromTape(b *testing.B) { + bobValidData, _ := bob.NewFromString(sampleValidBobTx) + for i := 0; i < b.N; i++ { + _ = NewFromTape(bobValidData.Out[0].Tape[2]) + } +} - 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 }` +// TestNewFromTapes will test the method NewFromTapes() +func TestNewFromTapes(t *testing.T) { + t.Parallel() - bobData, err := bob.NewFromString(sampleBobTx) + // Parse from string into BOB + bobValidData, err := bob.NewFromString(sampleValidBobTx) if err != nil { t.Fatalf("error occurred: %s", err.Error()) } + var bobInvalidData *bob.Tx + if bobInvalidData, err = bob.NewFromString(sampleInvalidBobTx); err != nil { + t.Fatalf("error occurred: %s", err.Error()) + } + + var ( + // Testing private methods + tests = []struct { + inputTapes []bob.Tape + expectedSignature string + expectedAlgorithm Algorithm + expectedComponent string + expectedData0 string + expectedData1 string + expectedData2 string + expectedNil bool + expectedValidation bool + }{ + { + bobValidData.Out[0].Tape, + "H+lubfcz5Z2oG8B7HwmP8Z+tALP+KNOPgedo7UTXwW8LBpMkgCgatCdpvbtf7wZZQSIMz83emmAvVS4S3F5X1wo=", + BitcoinECDSA, + "134a6TXxzgQ9Az3w8BcvgdZyA5UqRL89da", + "j", + "1BAPSuaPnfGnSBM3GLV9yhxUdYe4vGbdMT", + "ATTEST", + false, + true, + }, + { + bobInvalidData.Out[0].Tape, + "H+lubfcz5Z2oG8B7HwmP8Z+tALP+KNOPgedo7UTXwW8LBpMkgCgatCdpvbtf7wZZQSIMz83emmAvVS4S3F5X1wo=", + BitcoinECDSA, + "invalid-address", + "j", + "1BAPSuaPnfGnSBM3GLV9yhxUdYe4vGbdMT", + "ATTEST", + false, + false, + }, + { + []bob.Tape{*new(bob.Tape)}, + "", + "", + "", + "", + "", + "", + true, + false, + }, + } + ) + + // Run tests + for _, test := range tests { + if a := NewFromTapes(test.inputTapes); a == nil && !test.expectedNil { + t.Errorf("%s Failed: [%v] inputted and nil was not expected", t.Name(), test.inputTapes) + } else if a != nil && test.expectedNil { + t.Errorf("%s Failed: [%v] inputted and nil was expected", t.Name(), test.inputTapes) + } else if a != nil && a.Signature != test.expectedSignature { + t.Errorf("%s Failed: [%v] inputted and expected [%s] but got [%s]", t.Name(), test.inputTapes, test.expectedSignature, a.Signature) + } else if a != nil && a.Algorithm != test.expectedAlgorithm { + t.Errorf("%s Failed: [%v] inputted and expected [%s] but got [%s]", t.Name(), test.inputTapes, test.expectedAlgorithm, a.Algorithm) + } else if a != nil && a.AlgorithmSigningComponent != test.expectedComponent { + t.Errorf("%s Failed: [%v] inputted and expected [%s] but got [%s]", t.Name(), test.inputTapes, test.expectedComponent, a.AlgorithmSigningComponent) + } else if a != nil && len(a.Data) > 0 && a.Data[0] != test.expectedData0 { + t.Errorf("%s Failed: [%v] inputted and expected [%s] but got [%s]", t.Name(), test.inputTapes, test.expectedData0, a.Data[0]) + } else if a != nil && len(a.Data) > 0 && a.Data[1] != test.expectedData1 { + t.Errorf("%s Failed: [%v] inputted and expected [%s] but got [%s]", t.Name(), test.inputTapes, test.expectedData1, a.Data[1]) + } else if a != nil && len(a.Data) > 0 && a.Data[2] != test.expectedData2 { + t.Errorf("%s Failed: [%v] inputted and expected [%s] but got [%s]", t.Name(), test.inputTapes, test.expectedData2, a.Data[2]) + } else if a != nil && len(test.inputTapes) > 1 { + valid := ValidateTapes(test.inputTapes) + if valid && !test.expectedValidation { + t.Errorf("%s Failed: [%v] inputted and validation should have failed", t.Name(), test.inputTapes) + } else if !valid && test.expectedValidation { + t.Errorf("%s Failed: [%v] inputted and validation should have passed", t.Name(), test.inputTapes) + } + } + } +} - aip := &Aip{} - aip.SetDataFromTape(bobData.Out[0].Tape) +// ExampleNewFromTapes example using NewFromTapes() +func ExampleNewFromTapes() { + // Get BOB data from a TX + bobValidData, err := bob.NewFromString(sampleValidBobTx) + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } + + // Get from tape given the AIP index + a := NewFromTapes(bobValidData.Out[0].Tape) + + fmt.Printf("address: %s signature: %s", a.AlgorithmSigningComponent, a.Signature) + // Output:address: 134a6TXxzgQ9Az3w8BcvgdZyA5UqRL89da signature: H+lubfcz5Z2oG8B7HwmP8Z+tALP+KNOPgedo7UTXwW8LBpMkgCgatCdpvbtf7wZZQSIMz83emmAvVS4S3F5X1wo= +} - // 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.Fatalf("failed setting aip data %+v", aip.Data) +// BenchmarkNewFromTapes benchmarks the method NewFromTapes() +func BenchmarkNewFromTapes(b *testing.B) { + bobValidData, _ := bob.NewFromString(sampleValidBobTx) + for i := 0; i < b.N; i++ { + _ = NewFromTapes(bobValidData.Out[0].Tape) } }