Skip to content

SEP-10 Multisig Support#69

Merged
JakeUrban merged 50 commits intoastroband:masterfrom
JakeUrban:sep10-multisig
Mar 23, 2020
Merged

SEP-10 Multisig Support#69
JakeUrban merged 50 commits intoastroband:masterfrom
JakeUrban:sep10-multisig

Conversation

@JakeUrban
Copy link
Copy Markdown
Contributor

@JakeUrban JakeUrban commented Mar 10, 2020

resolves #68

Changes Summary:

  • Added the Stellar::SEP10 class, methods:
    • build_challenge_tx (moved from Stellar::Client)
    • read_challenge_tx (renamed from Stellar::Client.verify_challenge_tx)
    • verify_challenge_tx_signers
    • verify_challenge_tx_threshold
    • verify_challenge_tx
    • verify_tx_signatures
    • verify_tx_signed_by
  • build_challenge_tx, verify_challenge_tx, and verify_tx_signed_by, from Stellar::Client are now wrappers around the same functionality implemented in the SEP10 class.
  • added tests for multisig functionality

@leighmcculloch leighmcculloch self-requested a review March 10, 2020 23:38
@JakeUrban
Copy link
Copy Markdown
Contributor Author

@jimdanz I can't add you as a reviewer but you're welcome to take a look

end

# Confirm all signatures were consumed by a signer.
if signers_found.length != te.signatures.length - 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Does this allow for someone to sign multiple times with the same client key?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We remove duplicate signatures from the returned list, so no

@msfeldstein msfeldstein self-requested a review March 12, 2020 20:46
Copy link
Copy Markdown

@msfeldstein msfeldstein left a comment

Choose a reason for hiding this comment

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

Approved after putting the dedupe back and a test for it

@jimdanz
Copy link
Copy Markdown
Contributor

jimdanz commented Mar 13, 2020

👍

Copy link
Copy Markdown
Contributor

@leighmcculloch leighmcculloch left a comment

Choose a reason for hiding this comment

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

I haven't worked my way through the tests yet, but I looked through the non-test code. Highly recommend modeling the new functions and all the tests off the Go SDK as it more robustly deals performs signature verification and the tests cover lots of cases. There's a few places I've left a comment at that don't look like they cover all the cases that we handle in other SDKs, especially in regards to verifying signatures. Anything we can do to keep this similar to the Go SDK will help us maintain it too.

JakeUrban and others added 3 commits March 15, 2020 11:19
Co-Authored-By: Leigh McCulloch <leigh@mcchouse.com>
Co-Authored-By: Leigh McCulloch <leigh@mcchouse.com>
Co-Authored-By: Leigh McCulloch <leigh@mcchouse.com>
@JakeUrban
Copy link
Copy Markdown
Contributor Author

Should be good for another round of review @leighmcculloch 👍

Copy link
Copy Markdown
Contributor

@leighmcculloch leighmcculloch left a comment

Choose a reason for hiding this comment

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

There's a couple issues with the logic verifying signatures. There are also a few scenarios that aren't tested that it would be great to test. The Go and Java SDKs have a good set of test scenarios to follow as a reference that exercise all the weird and wonderful ways a transaction might be setup and how we'd want to behave.

end
end

describe "#verify_challenge_tx_threshold" do
Copy link
Copy Markdown
Contributor

@leighmcculloch leighmcculloch Mar 19, 2020

Choose a reason for hiding this comment

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

Could we add tests for these use cases?

  • not signed by server (error case)
  • signed by server and clients all signatures used but some signers aren't used (valid case) (Go SDK ref)
  • signed by server and clients all signatures used but some PreauthTxHash (T signer) and XHash (X signer) signers ignored (valid case) (Go SDK ref)
  • signatures unrecognized and can't be mapped to a signer (error case) (Go SDK ref)
  • no signers (error case)
  • no signatures (error case)

Copy link
Copy Markdown
Contributor Author

@JakeUrban JakeUrban Mar 19, 2020

Choose a reason for hiding this comment

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

All of these tests test functionality defined in other functions.

verify_challenge_tx_threshold is essentially a call to verify_challenge_tx_signers with the additional functionality of checking threshold. I guess this is more a question of testing philosophy, but I don't see the value in adding these tests for this function.

Even if verify_challenge_tx_threshold no longer called verify_challenge_tx_signers down the line, whatever it did call instead would presumably also have tests for its functionality. If we moved the implementation of said functionality into verify_challenge_tx_threshold, thats when it would be appropriate to add the unit tests there. In my opinion unit tests should be applied to wherever the functionality is implemented.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I often take the approach you describe, but the reason we were so thorough with tests in the other SDKs is because these functions are handling signature verification and authentication, so there is a lot at stake. The tests I listed are tests we have in the other SDKs.

Copy link
Copy Markdown
Contributor

@leighmcculloch leighmcculloch Mar 21, 2020

Choose a reason for hiding this comment

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

I missed asking for two tests on the _threshold function, which it looks like we also missed in the Go SDK, and strangely enough would catch a double counting of weight bug that I think exists in this function:

  • duplicate signers
  • duplicate signatures

Copy link
Copy Markdown
Contributor

@leighmcculloch leighmcculloch left a comment

Choose a reason for hiding this comment

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

The new tests look great 🎉 . I think there's a bug relating to double counting weights below and a test missing to catch that.

Comment on lines +262 to +268
signers.each do |s|
if !signer_strs_found.include?(s['key'])
next
end
signers_found.add(s)
weight += s['weight']
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think these lines result in double counting a weight because it is iterating over the input signers that may be an array containing duplicates. We should have a test that catches this case, specifically duplicate signers in the input list shouldn't cause an error but should only be counted once. We have a test that captures it for the _signers function but not this function and the bug exists in this function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added code to remove the signer key from signer_strs_found before adding its weight to the total. So if there is a duplicate signer in signers, it will be skipped the second time it appears in the loop.

end
end

describe "#verify_challenge_tx_threshold" do
Copy link
Copy Markdown
Contributor

@leighmcculloch leighmcculloch Mar 21, 2020

Choose a reason for hiding this comment

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

I missed asking for two tests on the _threshold function, which it looks like we also missed in the Go SDK, and strangely enough would catch a double counting of weight bug that I think exists in this function:

  • duplicate signers
  • duplicate signatures

Copy link
Copy Markdown
Contributor

@leighmcculloch leighmcculloch left a comment

Choose a reason for hiding this comment

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

The library code looks good 🎉 . The tests just added aren't really testing the library code because they're using a Set in the test, see below. Looks great otherwise.

if !signer_strs_found.include?(s['key'])
next
else
signer_strs_found.delete(s['key'])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a smart way of getting around the fact that you can't index efficiently into the original signers array. 👏 When I was originally looking at this code I didn't see a good way of doing this. This is so simple, love it.

Comment on lines 265 to 269
else
signer_strs_found.delete(s['key'])
end
signers_found.add(s)
weight += s['weight']
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What's the goal of having the delete inside the else? Everything else that runs in this block is essentially the else case because inside the if we call next that skips the rest of the block. Unless I'm missing something you could drop the else:

Suggested change
else
signer_strs_found.delete(s['key'])
end
signers_found.add(s)
weight += s['weight']
end
signer_strs_found.delete(s['key'])
signers_found.add(s)
weight += s['weight']

sep10.verify_challenge_tx_threshold(
challenge_xdr: challenge,
server: server_kp,
signers: Set[{'key' => client_kp.address, 'weight' => 1}],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you add a second version of this test that has the signers be an Array that contains the client kp twice?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can't pass Arrays, but I'll add a second signer with the same address but different weight. This will be deduplicated before passing to verify_challenge_tx_signers though, which builds the set of addresses.

Comment on lines +497 to +500
signers: Set[
{'key' => client_kp.address, 'weight' => 1},
{'key' => client_kp.address, 'weight' => 1}
],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This test isn't actually testing duplicate signers, it's testing a single signer because the Set type will automatically deduplicate these two entries. Take a look at this repl as an example.

You should use an Array instead of a Set here so the duplicates are preserved outside of the SDK, and the SDK should be the one responsible for handling duplicates. Favor arrays in tests to ensure the SDK does the deduplication necessary.

Suggested change
signers: Set[
{'key' => client_kp.address, 'weight' => 1},
{'key' => client_kp.address, 'weight' => 1}
],
signers: [
{'key' => client_kp.address, 'weight' => 1},
{'key' => client_kp.address, 'weight' => 1}
],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We can't pass arrays because the Contract will raise an exception if its not a Set, but I can use different weights so they're considered unique.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When this happens, verify_challenge_tx_threshold will use the weight specified in the first hash seen. Duplicate signers' weights will be ignored.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I missed that the Contract we've defined requires us to pass a Set. Got it!

Comment on lines +511 to +514
signers: Set[
{'key' => client_kp.address, 'weight' => 1},
{'key' => client_kp.address, 'weight' => 1}
],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here as above.

@JakeUrban JakeUrban merged commit b453494 into astroband:master Mar 23, 2020
@JakeUrban JakeUrban deleted the sep10-multisig branch March 23, 2020 20:48
@JakeUrban JakeUrban mentioned this pull request Mar 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update challenge transaction helpers for SEP-10 v1.3.0

5 participants