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

xdr, keypair: Add helpers to create CAP-40 decorated signatures #4302

Merged
merged 8 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions keypair/from_address.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ func (kp *FromAddress) SignDecorated(input []byte) (xdr.DecoratedSignature, erro
return xdr.DecoratedSignature{}, ErrCannotSign
}

func (kp *FromAddress) SignPayloadDecorated(input []byte) (xdr.DecoratedSignature, error) {
return xdr.DecoratedSignature{}, ErrCannotSign
}

func (kp *FromAddress) Equal(a *FromAddress) bool {
if kp == nil && a == nil {
return true
Expand Down
16 changes: 12 additions & 4 deletions keypair/full.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,18 @@ func (kp *Full) SignDecorated(input []byte) (xdr.DecoratedSignature, error) {
return xdr.DecoratedSignature{}, err
}

return xdr.DecoratedSignature{
Hint: xdr.SignatureHint(kp.Hint()),
Signature: xdr.Signature(sig),
}, nil
return xdr.NewDecoratedSignature(sig, kp.Hint()), nil
}

// SignPayloadDecorated returns a decorated signature using this key's hint and
// the last four bytes of both the input and the signature on that input.
Shaptic marked this conversation as resolved.
Show resolved Hide resolved
func (kp *Full) SignPayloadDecorated(input []byte) (xdr.DecoratedSignature, error) {
sig, err := kp.Sign(input)
if err != nil {
return xdr.DecoratedSignature{}, err
}

return xdr.NewDecoratedSignatureForPayload(sig, kp.Hint(), input), nil
}

func (kp *Full) Equal(f *Full) bool {
Expand Down
8 changes: 8 additions & 0 deletions keypair/full_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,12 @@ var _ = Describe("keypair.Full", func() {
})
})

Describe("SignPayloadDecorated()", func() {
It("returns the correct xdr struct", func() {
sig, err := subject.SignPayloadDecorated(message)
Expect(err).To(BeNil())
Expect(sig.Hint).To(BeEquivalentTo(payloadHint))
Expect(sig.Signature).To(BeEquivalentTo(signature))
})
})
})
1 change: 1 addition & 0 deletions keypair/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type KP interface {
Sign(input []byte) ([]byte, error)
SignBase64(input []byte) (string, error)
SignDecorated(input []byte) (xdr.DecoratedSignature, error)
SignPayloadDecorated(input []byte) (xdr.DecoratedSignature, error)
}

// Random creates a random full keypair
Expand Down
13 changes: 7 additions & 6 deletions keypair/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ func TestBuild(t *testing.T) {
}

var (
address = "GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H"
seed = "SDHOAMBNLGCE2MV5ZKIVZAQD3VCLGP53P3OBSBI6UN5L5XZI5TKHFQL4"
hint = [4]byte{0x56, 0xfc, 0x05, 0xf7}
message = []byte("hello")
signature = []byte{
0x2E, 0x75, 0xcc, 0x20, 0xd5, 0x19, 0x11, 0x1c, 0xaa, 0xaa, 0xdd, 0xdf,
address = "GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H"
seed = "SDHOAMBNLGCE2MV5ZKIVZAQD3VCLGP53P3OBSBI6UN5L5XZI5TKHFQL4"
hint = [4]byte{0x56, 0xfc, 0x05, 0xf7}
payloadHint = [4]byte{0x33, 0x90, 0x69, 0x98}
message = []byte("hello")
signature = []byte{
0x2e, 0x75, 0xcc, 0x20, 0xd5, 0x19, 0x11, 0x1c, 0xaa, 0xaa, 0xdd, 0xdf,
0x46, 0x4b, 0xb6, 0x50, 0xd2, 0xea, 0xf0, 0xa5, 0xd1, 0x8d, 0x74, 0x56,
0x93, 0xa1, 0x61, 0x00, 0xf2, 0xa4, 0x93, 0x7b, 0xc1, 0xdf, 0xfa, 0x8b,
0x0b, 0x1f, 0x61, 0xa2, 0x76, 0x99, 0x6d, 0x7e, 0xe8, 0xde, 0xb2, 0xd0,
Expand Down
38 changes: 38 additions & 0 deletions xdr/decorated_signature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package xdr

// NewDecoratedSignature constructs a decorated signature structure directly
// from the given signature and key hint. Note that the key hint should
// correspond to the key that created the signature, but this helper cannot
// ensure that.
Shaptic marked this conversation as resolved.
Show resolved Hide resolved
func NewDecoratedSignature(sig []byte, keyHint [4]byte) DecoratedSignature {
return DecoratedSignature{
Hint: SignatureHint(keyHint),
Shaptic marked this conversation as resolved.
Show resolved Hide resolved
Signature: Signature(sig),
}
}

// NewDecoratedSignatureForPayload creates a decorated signature with a hint
// that uses the key hint, the last four bytes of signature, and the last four
// bytes of the input that got signed. Note that the signature should be the
// signature of the payload via the key being hinted, but this construction
// method cannot ensure that.
func NewDecoratedSignatureForPayload(
sig []byte, keyHint [4]byte, payload []byte,
) DecoratedSignature {
hint := [4]byte{}
// copy the last four bytes of the payload into the hint
if len(payload) >= len(hint) {
copy(hint[:], payload[len(payload)-4:])
Shaptic marked this conversation as resolved.
Show resolved Hide resolved
} else {
copy(hint[:], payload[:])
}

for i := 0; i < len(keyHint); i++ {
hint[i] ^= keyHint[i]
}

return DecoratedSignature{
Hint: SignatureHint(hint),
Signature: Signature(sig),
}
}
50 changes: 50 additions & 0 deletions xdr/decorated_signature_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package xdr_test

import (
"fmt"
"testing"

"github.com/stellar/go/xdr"
"github.com/stretchr/testify/assert"
)

func TestDecoratedSignatures(t *testing.T) {
signature := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8}
keyHint := [4]byte{9, 10, 11, 12}

testCases := []struct {
payload []byte
expectedHint [4]byte
expectedPayloadHint [4]byte
}{
{
[]byte{13, 14, 15, 16, 17, 18, 19, 20, 21},
keyHint,
[4]byte{27, 25, 31, 25},
},
{
[]byte{18, 19, 20},
keyHint,
[4]byte{27, 25, 31, 12},
},
}

for _, testCase := range testCases {
t.Run(
fmt.Sprintf("%d-byte decorated sig", len(testCase.payload)),
func(t *testing.T) {
decoSig := xdr.NewDecoratedSignature(signature, keyHint)
assert.EqualValues(t, testCase.expectedHint, decoSig.Hint)
assert.EqualValues(t, signature, decoSig.Signature)
})

t.Run(
fmt.Sprintf("%d-byte decorated sig for payload", len(testCase.payload)),
func(t *testing.T) {
decoSig := xdr.NewDecoratedSignatureForPayload(
signature, keyHint, testCase.payload)
assert.EqualValues(t, testCase.expectedPayloadHint, decoSig.Hint)
assert.EqualValues(t, signature, decoSig.Signature)
})
}
}
Copy link
Member

@leighmcculloch leighmcculloch Mar 23, 2022

Choose a reason for hiding this comment

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

❗ The first t.Run block is run twice with the same inputs, but then asserts on separate rows in the table that just happened to be constant:

decoSig := xdr.NewDecoratedSignature(signature, keyHint)

As mentioned before, there's unnecessary uncoupling testing these two functions which is resulting in a set of tests that are not really clear. Can you split them up?

For example, if you remove the table test framework that is driven by a mixture of shared inputs and unique inputs, you end up with the following that while is less clever, is approximately the same amount of code:

Suggested change
func TestDecoratedSignatures(t *testing.T) {
signature := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8}
keyHint := [4]byte{9, 10, 11, 12}
testCases := []struct {
payload []byte
expectedHint [4]byte
expectedPayloadHint [4]byte
}{
{
[]byte{13, 14, 15, 16, 17, 18, 19, 20, 21},
keyHint,
[4]byte{27, 25, 31, 25},
},
{
[]byte{18, 19, 20},
keyHint,
[4]byte{27, 25, 31, 12},
},
}
for _, testCase := range testCases {
t.Run(
fmt.Sprintf("%d-byte decorated sig", len(testCase.payload)),
func(t *testing.T) {
decoSig := xdr.NewDecoratedSignature(signature, keyHint)
assert.EqualValues(t, testCase.expectedHint, decoSig.Hint)
assert.EqualValues(t, signature, decoSig.Signature)
})
t.Run(
fmt.Sprintf("%d-byte decorated sig for payload", len(testCase.payload)),
func(t *testing.T) {
decoSig := xdr.NewDecoratedSignatureForPayload(
signature, keyHint, testCase.payload)
assert.EqualValues(t, testCase.expectedPayloadHint, decoSig.Hint)
assert.EqualValues(t, signature, decoSig.Signature)
})
}
}
func TestNewDecoratedSiganture(t *testing.T) {
ds := NewDecoratedSignature(
[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8},
[4]byte{9, 10, 11, 12},
)
assert.Equal(
t,
DecoratedSignature{
Hint: [4]byte{9, 10, 11, 12},
Signature: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8},
},
ds,
)
}
func TestNewDecoratedSigantureForPayload_payload4OrMore(t *testing.T) {
ds := NewDecoratedSignatureForPayload(
[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8},
[4]byte{9, 10, 11, 12},
[]byte{13, 14, 15, 16, 17, 18, 19, 20, 21},
)
assert.Equal(
t,
DecoratedSignature{
Hint: [4]byte{27, 25, 31, 25},
Signature: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8},
},
ds,
)
}
func TestNewDecoratedSigantureForPayload_payloadLessThan4(t *testing.T) {
ds := NewDecoratedSignatureForPayload(
[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8},
[4]byte{9, 10, 11, 12},
[]byte{18, 19, 20},
)
assert.Equal(
t,
DecoratedSignature{
Hint: [4]byte{27, 25, 31, 12},
Signature: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8},
},
ds,
)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I see what you're getting at. Wdyt about d37806d? Keeps the extensible table of tests (and adds a length-zero test), but completely separates it from the NewDecoratedSignature test, and minimizes the scattered "magic byte arrays".

Copy link
Contributor Author

@Shaptic Shaptic Mar 23, 2022

Choose a reason for hiding this comment

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

(I recommend viewing the whole file rather than the commit diff.)

5 changes: 2 additions & 3 deletions xdr/signer_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ func TestSignerKey_SetAddress(t *testing.T) {
Name string
Address string
}{

{
Name: "AccountID",
Address: "GA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQHES5",
Expand All @@ -69,8 +68,8 @@ func TestSignerKey_SetAddress(t *testing.T) {
Address: "XBU2RRGLXH3E5CQHTD3ODLDF2BWDCYUSSBLLZ5GNW7JXHDIYKXZWGTOG",
},
{
"SignedPayload",
"PA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAOQCAQDAQCQMBYIBEFAWDANBYHRAEISCMKBKFQXDAMRUGY4DUAAAAFGBU",
Name: "SignedPayload",
Address: "PA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAOQCAQDAQCQMBYIBEFAWDANBYHRAEISCMKBKFQXDAMRUGY4DUAAAAFGBU",
},
}

Expand Down