Skip to content

Commit

Permalink
Add additional api methods
Browse files Browse the repository at this point in the history
  • Loading branch information
KonradStaniec committed Aug 18, 2022
1 parent 95666f5 commit 290435a
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 43 deletions.
106 changes: 86 additions & 20 deletions btctxformatter/formatter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package btctxformatter

import (
"bytes"
"crypto/sha256"
"encoding/binary"
"errors"
Expand All @@ -16,33 +17,43 @@ type formatHeader struct {
part uint8
}

type BabylonData struct {
data []byte
index uint8
}

const (
TestTag BabylonTag = "BBT"

MainTag BabylonTag = "BBN"

CurrentVersion FormatVersion = 0

firstPartNumber uint8 = 0
firstPartIndex uint8 = 0

secondPartNumber uint8 = 1
secondPartIndex uint8 = 1

HeaderLength = 4
headerLength = 4

LastCommitHashLength = 32

BitMapLength = 13

AddressLength = 20

// 8 bytes are for 64bit unsigned epoch number
FirstHalfLength = HeaderLength + LastCommitHashLength + AddressLength + 8 + BitMapLength
// Each checkpoint is composed of two parts
NumberOfParts = 2

HashLength = 10
hashLength = 10

BlsSigLength = 48

SecondHalfLength = HeaderLength + BlsSigLength + HashLength
// 8 bytes are for 64bit unsigned epoch number
firstPartLength = headerLength + LastCommitHashLength + AddressLength + 8 + BitMapLength

secondPartLength = headerLength + BlsSigLength + hashLength

ApplicationDataLength = firstPartLength + secondPartLength - headerLength - headerLength - hashLength
)

func getVerHalf(version FormatVersion, halfNumber uint8) uint8 {
Expand All @@ -69,7 +80,7 @@ func u64ToBEBytes(u uint64) []byte {
return bytes
}

func encodeFirstTx(
func encodeFirstOpRetrun(
tag BabylonTag,
version FormatVersion,
epoch uint64,
Expand All @@ -80,7 +91,7 @@ func encodeFirstTx(

var serializedBytes = []byte{}

serializedBytes = append(serializedBytes, encodeHeader(tag, version, firstPartNumber)...)
serializedBytes = append(serializedBytes, encodeHeader(tag, version, firstPartIndex)...)

serializedBytes = append(serializedBytes, u64ToBEBytes(epoch)...)

Expand All @@ -95,24 +106,24 @@ func encodeFirstTx(

func getCheckSum(firstTxBytes []byte) []byte {
hash := sha256.Sum256(firstTxBytes)
return hash[0:HashLength]
return hash[0:hashLength]
}

func encodeSecondTx(
func encodeSecondOpReturn(
tag BabylonTag,
version FormatVersion,
firstTxBytes []byte,
firstOpReturnBytes []byte,
blsSig []byte,
) []byte {
var serializedBytes = []byte{}

serializedBytes = append(serializedBytes, encodeHeader(tag, version, secondPartNumber)...)
serializedBytes = append(serializedBytes, encodeHeader(tag, version, secondPartIndex)...)

serializedBytes = append(serializedBytes, blsSig...)

// we are calculating checksum only from application data, without header, as header is always
// the same.
serializedBytes = append(serializedBytes, getCheckSum(firstTxBytes[4:])...)
serializedBytes = append(serializedBytes, getCheckSum(firstOpReturnBytes[headerLength:])...)

return serializedBytes
}
Expand Down Expand Up @@ -151,9 +162,9 @@ func EncodeCheckpointData(
return nil, nil, errors.New("BlsSig should have 48 bytes")
}

var firstHalf = encodeFirstTx(tag, version, epoch, lastCommitHash, bitmap, submitterAddress)
var firstHalf = encodeFirstOpRetrun(tag, version, epoch, lastCommitHash, bitmap, submitterAddress)

var secondHalf = encodeSecondTx(tag, version, firstHalf, blsSig)
var secondHalf = encodeSecondOpReturn(tag, version, firstHalf, blsSig)

return firstHalf, secondHalf, nil
}
Expand Down Expand Up @@ -223,14 +234,14 @@ func GetCheckpointData(
}

if version > CurrentVersion {
return nil, errors.New("invalid part number")
return nil, errors.New("not supported version")
}

if partNumber == 0 && len(data) != FirstHalfLength {
if partNumber == 0 && len(data) != firstPartLength {
return nil, errors.New("invalid length. First part should have 77 bytes")
}

if partNumber == 1 && len(data) != SecondHalfLength {
if partNumber == 1 && len(data) != secondPartLength {
return nil, errors.New("invalid length. First part should have 62 bytes")
}

Expand All @@ -244,11 +255,66 @@ func GetCheckpointData(

// At this point this is probable babylon data, strip the header and return data
// to the caller
dataWithoutHeader := data[4:]
dataWithoutHeader := data[headerLength:]

dataNoHeader := make([]byte, len(dataWithoutHeader))

copy(dataNoHeader, dataWithoutHeader)

return dataNoHeader, nil
}

// IsBabylonCheckpointData Checks if given bytearray is potential babylon data,
// if it is then returns index of data along side with data itself
func IsBabylonCheckpointData(
tag BabylonTag,
version FormatVersion,
data []byte,
) (*BabylonData, error) {

var idx uint8 = 0

for idx < NumberOfParts {
data, err := GetCheckpointData(tag, version, idx, data)

if err == nil {
bd := BabylonData{data: data, index: idx}
return &bd, nil
}

idx++
}

return nil, errors.New("not valid babylon data")
}

func ConnectParts(version FormatVersion, f []byte, s []byte) ([]byte, error) {
if version > CurrentVersion {
return nil, errors.New("not supported version")
}

if len(f) != firstPartLength-headerLength {
return nil, errors.New("not valid first part")
}

if len(s) != secondPartLength-headerLength {
return nil, errors.New("not valid second part")
}

firstHash := sha256.Sum256(f)

hashStartIdx := len(s) - hashLength

expectedHash := s[hashStartIdx:]

if !bytes.Equal(firstHash[:hashLength], expectedHash) {
return nil, errors.New("parts do not connect")
}

var dst []byte
// TODO this is not supper efficient
dst = append(dst, f...)
dst = append(dst, s[:hashStartIdx]...)

return dst, nil
}
28 changes: 14 additions & 14 deletions btctxformatter/formatter_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package btctxformatter

import (
"bytes"
"crypto/rand"
"crypto/sha256"
"testing"
)

Expand All @@ -28,31 +26,33 @@ func TestEncodeMainCheckpointData(t *testing.T) {
t.Errorf("Valid data should be properly encoded")
}

if len(firstHalf) != FirstHalfLength {
t.Errorf("Encoded first half should have %d bytes, have %d", FirstHalfLength, len(firstHalf))
if len(firstHalf) != firstPartLength {
t.Errorf("Encoded first half should have %d bytes, have %d", firstPartLength, len(firstHalf))
}

if len(secondHalf) != SecondHalfLength {
t.Errorf("Encoded second half should have %d bytes, have %d", SecondHalfLength, len(secondHalf))
if len(secondHalf) != secondPartLength {
t.Errorf("Encoded second half should have %d bytes, have %d", secondPartLength, len(secondHalf))
}

decodedFirst, err := GetCheckpointData(MainTag, CurrentVersion, 0, firstHalf)
decodedFirst, err := IsBabylonCheckpointData(MainTag, CurrentVersion, firstHalf)

if err != nil {
t.Errorf("Valid data should be properly decoded")
}

decodedSecond, err := GetCheckpointData(MainTag, CurrentVersion, 1, secondHalf)
decodedSecond, err := IsBabylonCheckpointData(MainTag, CurrentVersion, secondHalf)

if err != nil {
t.Errorf("Valid data should be properly decoded")
}

firstHalfCheckSum := sha256.Sum256(decodedFirst)

checksumPart := firstHalfCheckSum[0:HashLength]
data, err := ConnectParts(CurrentVersion, decodedFirst.data, decodedSecond.data)

checksumPartFromDec := decodedSecond[len(decodedSecond)-10:]
if err != nil {
t.Errorf("Parts should match. Error: %v", err)
}

if !bytes.Equal(checksumPart, checksumPartFromDec) {
t.Errorf("Calculated checksum of first half should equal checksum attached to second half")
if len(data) != ApplicationDataLength {
t.Errorf("Not expected application level data length. Have: %d, want: %d", len(data), ApplicationDataLength)
}
}
12 changes: 3 additions & 9 deletions x/btccheckpoint/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
fmt "fmt"
"math/big"

txformat "github.com/babylonchain/babylon/btctxformatter"
"github.com/babylonchain/babylon/x/btccheckpoint/btcutils"
btcchaincfg "github.com/btcsuite/btcd/chaincfg"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -15,22 +16,15 @@ var (
_ sdk.Msg = (*MsgInsertBTCSpvProof)(nil)
)

const (
// two proofs is babylon specific not bitcoin specific, that why it is defined
// here not in btcutils
// This could also be a parameter in config. At this time babylon expect 2,
// OP_RETRUN transactions with valid proofs.
expectedProofs = 2
)

// Parse and Validate transactions which should contain OP_RETURN data.
// OP_RETURN bytes are not validated in any way. It is up to the caller attach
// semantic meaning and validity to those bytes.
// Returned ParsedProofs are in same order as raw proofs
// TODO explore possibility of validating that output in second tx is payed by
// input in the first tx
func ParseTwoProofs(submitter sdk.AccAddress, proofs []*BTCSpvProof, powLimit *big.Int) (*RawCheckpointSubmission, error) {
if len(proofs) != expectedProofs {
// Expecting as many proofs as many parts our checkpoint is composed of
if len(proofs) != txformat.NumberOfParts {
return nil, fmt.Errorf("expected at exactly valid op return transactions")
}

Expand Down

0 comments on commit 290435a

Please sign in to comment.