-
Notifications
You must be signed in to change notification settings - Fork 500
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
Sign method - verify accurate encoding #1026
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how to test that this works as intended but I do wonder how many edge cases like with Flags might break it.
src/transaction/sign.ts
Outdated
const decoded = binary.decode(serialized) | ||
|
||
// ...And ensure it is equal to the original tx, except: | ||
// - It must have a TxnSignature or txSigners (multisign). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about flags? The "standard" way of serializing a transaction usually adds tfFullyCanonicalSig even if the user didn't specify it. I could see that feature accidentally tripping this check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great question. I think that if the user didn't specify tfFullyCanonicalSig
then ripple-lib's sign
method does not add it. However, the prepare*
methods -- including prepareTransaction
-- do: https://github.com/ripple/ripple-lib/blob/develop/src/transaction/utils.ts#L67
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought if you left Flags
off entirely it did provide it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I've verified that signing a transaction with ripple-lib's
sign
method (which also serializes it) does not add tfFullyCanonicalSig, regardless of whether the user specified Flags or not. But note that theprepare*
methods do add it. - I've added a test that ensures that no Flags are added: c5714de
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that despite the prepare*
methods, the sign
method should also add Flags
if none are specified, mainly because our documentation says we do: https://xrpl.org/transaction-malleability.html
To further protect users, Ripple has configured its code to enable the
tfFullyCanonicalSig
flag by default where possible. Ripple strongly encourages third-party implementations of XRP Ledger software to generate only fully-canonical signatures, and enable tfFullyCanonicalSig on transactions by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I'm currently aware, the only case that this will catch occurs when an amount of XRP drops contains a decimal point; and this will be caught ahead of time with ripple-binary-codec 0.2.2. (This PR also updates ripple-lib to use ripple-binary-codec 0.2.2.) This PR incorporates the secondary check as "defense in depth". Here's how I tested it:
I also verified that the decoded transaction's Fee is checked (to ensure it's not higher than maxFeeXRP) by temporarily commenting out the first Fee check (which checks the original txJSON prior to signing/encoding). |
src/transaction/sign.ts
Outdated
@@ -1,3 +1,4 @@ | |||
import * as isEqual from '../common/js/lodash.isequal' | |||
import * as utils from './utils' | |||
import keypairs = require('ripple-keypairs') | |||
import binary = require('ripple-binary-codec') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional: Consider rebasing this import to binaryCodec because it makes the code below easier to read IMO:
const decoded = binary.decode(serialized)
vs
const decoded = binaryCodec.decode(serialized)
src/transaction/sign.ts
Outdated
// - It must have a TxnSignature or txSigners (multisign). | ||
if (!decoded.TxnSignature && !tx.Signers) { | ||
throw new utils.common.errors.ValidationError( | ||
'Serialized signed transaction is missing "TxnSignature" property' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider rephrasing to line up with the check you're actually performing:
"Serialized transaction must have a TxnSignature or Signers property."
src/transaction/sign.ts
Outdated
const decoded = binary.decode(serialized) | ||
|
||
// ...And ensure it is equal to the original tx, except: | ||
// - It must have a TxnSignature or txSigners (multisign). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Refer to this property the same way you refer to it in code: 'Signers' vs txSigners, or at the very least 'tx.Signers'.
Or, if you think it should be called txSigners, you could refactor the code to this property name.
src/transaction/sign.ts
Outdated
} | ||
|
||
// - We know that the original tx did not have Signers, so if it exists, we should delete it: | ||
delete decoded.Signers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've already deleted this on line 66.
Some initial (mostly stylistic) comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM - Please wait for another reviewer to stamp as well since I'm still relatively new to these flows.
Thanks to @mDuo13 for taking another look! I've also done some additional manual testing myself, and everything looks good. |
Hi, I am using values with standard amount of digits after the '.' like e.g. "3.141000". It turns out that this fails the verification. It was easy for me to fix, but if you feel like values with zeros should be accepted, then you might want to change it. The following code will show the issue: const {RippleAPI} = require('ripple-lib')
api = new RippleAPI({ server: 'wss://s1.ripple.com' });
const address = "rrrrrrrrrrrrrrrrrNAMEtxvNvQ"
const secret = "shNs3GgHxoNPRWL6chhwH5s5A97T5"
const order = {
"direction": "sell",
"quantity": {
"currency": "USD",
"counterparty": 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
"value": "3.140000"
},
"totalPrice": {
"currency": "XRP",
"value": "31415"
}
};
(async () => {
await api.connect()
const accountInfo = await api.getAccountInfo(address)
const prepared = await api.prepareOrder(address, order, { "sequence": accountInfo.sequence } )
const {signedTransaction} = api.sign(prepared.txJSON, secret);
api.disconnect()
})().catch(err => {
console.error(err.message, '\n', JSON.stringify(err.data, null, 2))
}) |
@jnr101 Thanks for pointing that out! I put some thought into it and decided to keep the current behavior because it's risky (and potentially unexpected) for the encoded transaction to contain different values than were originally provided to the To make this behavior more discoverable, I improved the error message and added a diff so that users can quickly see where the discrepancy lies: #1037 |
Hi @intelliot, sounds good to me. I have run the tests and looks good 👍 |
@yxxyun Correct. The underlying transaction format is in drops and thus does not allow more than 6 decimal places for XRP amounts. It would be risky for the library to silently/automatically do rounding or truncation, so apps/clients must do that prior to passing the amount to ripple-lib. Depending on the context/use case, different rounding policies are possible. Hope this helps! |
edited, |
@yxxyun Could you create a new issue describing the problem? Note that you can specify |
When signing a transaction, we should decode the signedTransaction blob and check the integrity (accuracy) of the data.