Conversation
|
@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 |
There was a problem hiding this comment.
Does this allow for someone to sign multiple times with the same client key?
There was a problem hiding this comment.
We remove duplicate signatures from the returned list, so no
msfeldstein
left a comment
There was a problem hiding this comment.
Approved after putting the dedupe back and a test for it
|
👍 |
leighmcculloch
left a comment
There was a problem hiding this comment.
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.
Co-Authored-By: Leigh McCulloch <leigh@mcchouse.com>
Co-Authored-By: Leigh McCulloch <leigh@mcchouse.com>
Co-Authored-By: Leigh McCulloch <leigh@mcchouse.com>
|
Should be good for another round of review @leighmcculloch 👍 |
leighmcculloch
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Co-Authored-By: Leigh McCulloch <leigh@mcchouse.com>
Co-Authored-By: Leigh McCulloch <leigh@mcchouse.com>
Co-Authored-By: Leigh McCulloch <leigh@mcchouse.com>
| signers.each do |s| | ||
| if !signer_strs_found.include?(s['key']) | ||
| next | ||
| end | ||
| signers_found.add(s) | ||
| weight += s['weight'] | ||
| end |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
leighmcculloch
left a comment
There was a problem hiding this comment.
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.
lib/stellar/sep10.rb
Outdated
| if !signer_strs_found.include?(s['key']) | ||
| next | ||
| else | ||
| signer_strs_found.delete(s['key']) |
There was a problem hiding this comment.
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.
lib/stellar/sep10.rb
Outdated
| else | ||
| signer_strs_found.delete(s['key']) | ||
| end | ||
| signers_found.add(s) | ||
| weight += s['weight'] |
There was a problem hiding this comment.
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:
| 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}], |
There was a problem hiding this comment.
Could you add a second version of this test that has the signers be an Array that contains the client kp twice?
There was a problem hiding this comment.
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.
| signers: Set[ | ||
| {'key' => client_kp.address, 'weight' => 1}, | ||
| {'key' => client_kp.address, 'weight' => 1} | ||
| ], |
There was a problem hiding this comment.
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.
| 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} | |
| ], |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
When this happens, verify_challenge_tx_threshold will use the weight specified in the first hash seen. Duplicate signers' weights will be ignored.
There was a problem hiding this comment.
Ah, I missed that the Contract we've defined requires us to pass a Set. Got it!
| signers: Set[ | ||
| {'key' => client_kp.address, 'weight' => 1}, | ||
| {'key' => client_kp.address, 'weight' => 1} | ||
| ], |
There was a problem hiding this comment.
Same here as above.
resolves #68
Changes Summary:
Stellar::SEP10class, methods:build_challenge_tx(moved fromStellar::Client)read_challenge_tx(renamed fromStellar::Client.verify_challenge_tx)verify_challenge_tx_signersverify_challenge_tx_thresholdverify_challenge_txverify_tx_signaturesverify_tx_signed_bybuild_challenge_tx,verify_challenge_tx, andverify_tx_signed_by,from Stellar::Clientare now wrappers around the same functionality implemented in theSEP10class.