Skip to content

Commit 2789d7d

Browse files
committed
feat(bcdc): add EncodeForSigningBatch function and related tests
1 parent 73334b9 commit 2789d7d

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

binary-codec/codec.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,21 @@ var (
1717

1818
// ErrSigningClaimFieldNotFound is returned when the 'Channel' & 'Amount' fields are both required, but were not found.
1919
ErrSigningClaimFieldNotFound = errors.New("'Channel' & 'Amount' fields are both required, but were not found")
20+
// ErrBatchFlagsFieldNotFound is returned when the 'flags' field is missing.
21+
ErrBatchFlagsFieldNotFound = errors.New("no field `flags`")
22+
// ErrBatchTxIDsFieldNotFound is returned when the 'txIDs' field is missing.
23+
ErrBatchTxIDsFieldNotFound = errors.New("no field `txIDs`")
24+
// ErrBatchTxIDsNotArray is returned when the 'txIDs' field is not an array.
25+
ErrBatchTxIDsNotArray = errors.New("txIDs field must be an array")
26+
// ErrBatchTxIDNotString is returned when a txID is not a string.
27+
ErrBatchTxIDNotString = errors.New("each txID must be a string")
2028
)
2129

2230
const (
2331
txMultiSigPrefix = "534D5400"
2432
paymentChannelClaimPrefix = "434C4D00"
2533
txSigPrefix = "53545800"
34+
batchPrefix = "42434800"
2635
)
2736

2837
// Encode converts a JSON transaction object to a hex string in the canonical binary format.
@@ -116,6 +125,55 @@ func EncodeForSigningClaim(json map[string]any) (string, error) {
116125
return strings.ToUpper(paymentChannelClaimPrefix + hex.EncodeToString(channel) + hex.EncodeToString(amount)), nil
117126
}
118127

128+
// EncodeForSigningBatch encodes a batch transaction into binary format in preparation for signing.
129+
func EncodeForSigningBatch(json map[string]any) (string, error) {
130+
if json["flags"] == nil {
131+
return "", ErrBatchFlagsFieldNotFound
132+
}
133+
if json["txIDs"] == nil {
134+
return "", ErrBatchTxIDsFieldNotFound
135+
}
136+
137+
// Extract and validate txIDs
138+
txIDsInterface, ok := json["txIDs"].([]any)
139+
if !ok {
140+
return "", ErrBatchTxIDsNotArray
141+
}
142+
143+
// Create UInt32 for flags
144+
flagsType := &types.UInt32{}
145+
flagsBytes, err := flagsType.FromJSON(json["flags"])
146+
if err != nil {
147+
return "", err
148+
}
149+
150+
// Create UInt32 for txIDs length
151+
txIDsLengthType := &types.UInt32{}
152+
txIDsLengthBytes, err := txIDsLengthType.FromJSON(uint32(len(txIDsInterface)))
153+
if err != nil {
154+
return "", err
155+
}
156+
157+
// Build the result string
158+
result := batchPrefix + hex.EncodeToString(flagsBytes) + hex.EncodeToString(txIDsLengthBytes)
159+
160+
// Add each transaction ID
161+
for _, txID := range txIDsInterface {
162+
txIDStr, ok := txID.(string)
163+
if !ok {
164+
return "", ErrBatchTxIDNotString
165+
}
166+
hash256 := types.NewHash256()
167+
txIDBytes, err := hash256.FromJSON(txIDStr)
168+
if err != nil {
169+
return "", err
170+
}
171+
result += hex.EncodeToString(txIDBytes)
172+
}
173+
174+
return strings.ToUpper(result), nil
175+
}
176+
119177
// removeNonSigningFields removes the fields from a JSON transaction object that should not be signed.
120178
func removeNonSigningFields(json map[string]any) map[string]any {
121179
for k := range json {

binary-codec/codec_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,3 +648,91 @@ func TestEncodeForSigning(t *testing.T) {
648648
})
649649
}
650650
}
651+
652+
func TestEncodeForSigningBatch(t *testing.T) {
653+
tt := []struct {
654+
description string
655+
input map[string]any
656+
output string
657+
expectedErr error
658+
}{
659+
{
660+
description: "pass - encode batch with multiple txIDs",
661+
input: map[string]any{
662+
"flags": uint32(1),
663+
"txIDs": []any{
664+
"ABE4871E9083DF66727045D49DEEDD3A6F166EB7F8D1E92FE868F02E76B2C5CA",
665+
"795AAC88B59E95C3497609749127E69F12958BC016C600C770AEEB1474C840B4",
666+
},
667+
},
668+
output: "4243480000000001" + // hash prefix + flags
669+
"00000002" + // txIds length
670+
"ABE4871E9083DF66727045D49DEEDD3A6F166EB7F8D1E92FE868F02E76B2C5CA" + // first txID
671+
"795AAC88B59E95C3497609749127E69F12958BC016C600C770AEEB1474C840B4", // second txID
672+
expectedErr: nil,
673+
},
674+
{
675+
description: "pass - encode batch with zero flags",
676+
input: map[string]any{
677+
"flags": uint32(0),
678+
"txIDs": []any{
679+
"1111111111111111111111111111111111111111111111111111111111111111",
680+
},
681+
},
682+
output: "4243480000000000000000011111111111111111111111111111111111111111111111111111111111111111",
683+
expectedErr: nil,
684+
},
685+
{
686+
description: "fail - batch missing flags field",
687+
input: map[string]any{
688+
"txIDs": []any{
689+
"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879",
690+
},
691+
},
692+
output: "",
693+
expectedErr: ErrBatchFlagsFieldNotFound,
694+
},
695+
{
696+
description: "fail - batch missing txIDs field",
697+
input: map[string]any{
698+
"flags": uint32(12345),
699+
},
700+
output: "",
701+
expectedErr: ErrBatchTxIDsFieldNotFound,
702+
},
703+
{
704+
description: "fail - batch with invalid txIDs type",
705+
input: map[string]any{
706+
"flags": uint32(12345),
707+
"txIDs": "not_an_array",
708+
},
709+
output: "",
710+
expectedErr: ErrBatchTxIDsNotArray,
711+
},
712+
{
713+
description: "fail - batch with non-string txID",
714+
input: map[string]any{
715+
"flags": uint32(12345),
716+
"txIDs": []any{
717+
123, // not a string
718+
},
719+
},
720+
output: "",
721+
expectedErr: ErrBatchTxIDNotString,
722+
},
723+
}
724+
725+
for _, tc := range tt {
726+
t.Run(tc.description, func(t *testing.T) {
727+
got, err := EncodeForSigningBatch(tc.input)
728+
729+
if tc.expectedErr != nil {
730+
require.EqualError(t, err, tc.expectedErr.Error())
731+
require.Empty(t, got)
732+
} else {
733+
require.NoError(t, err)
734+
require.Equal(t, tc.output, got)
735+
}
736+
})
737+
}
738+
}

0 commit comments

Comments
 (0)