-
Notifications
You must be signed in to change notification settings - Fork 14
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
chore: storage method to sign tx with custom override #618
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #618 +/- ##
==========================================
+ Coverage 79.14% 79.23% +0.08%
==========================================
Files 73 73
Lines 5731 5749 +18
Branches 1214 1217 +3
==========================================
+ Hits 4536 4555 +19
+ Misses 1178 1177 -1
Partials 17 17 ☔ View full report in Codecov by Sentry. |
@@ -1571,7 +1571,7 @@ class HathorWallet extends EventEmitter { | |||
isCreateNFT: newOptions.isCreateNFT, | |||
}, | |||
); | |||
return await transactionUtils.prepareTransaction( | |||
return transactionUtils.prepareTransaction( |
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.
note: These are just to remove unnecessary awaits.
src/new/wallet.js
Outdated
async getSignatures(tx, { pinCode = null } = {}) { | ||
if (await this.storage.isReadonly()) { | ||
throw new WalletFromXPubGuard('getSignatures'); | ||
} | ||
const pin = pinCode || this.pinCode; | ||
if (!pin) { | ||
throw new Error(ERROR_MESSAGE_PIN_REQUIRED); | ||
} | ||
|
||
const xprivkey = await this.storage.getMainXPrivKey(pin); | ||
const key = HDPrivateKey(xprivkey); | ||
|
||
const signatures = []; | ||
const dataToSignHash = tx.getDataToSignHash(); | ||
for (const indexes of await this.getWalletInputInfo(tx)) { | ||
const { addressIndex } = indexes; | ||
// Derive key to addressIndex | ||
const derivedKey = key.deriveNonCompliantChild(addressIndex); | ||
const privateKey = derivedKey.privateKey; | ||
// Get tx signature and populate transaction | ||
const sigDER = transactionUtils.getSignature(dataToSignHash, privateKey); | ||
signatures.push({ | ||
signature: sigDER.toString('hex'), | ||
pubkey: privateKey.publicKey.toString(), | ||
...indexes, | ||
}); | ||
} | ||
|
||
return signatures; | ||
const signatures = await this.storage.signTxP2PKH(tx, pin); | ||
return signatures.map(sigData => ({ | ||
...sigData, | ||
addressPath: this.getAddressPathForIndex(sigData.addressIndex), | ||
})); |
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.
note: This will allow us to sign transactions for P2SH wallets using either the bitcore signing from the storage mainKey or the custom signing method.
async getAddressPubkey(index: number): Promise<string> { | ||
const addressInfo = await this.store.getAddressAtIndex(index); | ||
if (addressInfo?.publicKey) { | ||
// public key already cached on address info | ||
return addressInfo.publicKey; | ||
} | ||
|
||
// derive public key from xpub | ||
const accessData = await this._getValidAccessData(); | ||
const hdpubkey = new HDPublicKey(accessData.xpubkey); | ||
const key: HDPublicKey = hdpubkey.deriveChild(index); | ||
return key.publicKey.toString('hex'); | ||
} |
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.
note: We only got the pubkey from the derived mainKey, this does not work for external signing where we only have the xpubkey.
@@ -123,12 +123,13 @@ const transaction = { | |||
return signature.toDER(); | |||
}, | |||
|
|||
async signTransaction(tx: Transaction, storage: IStorage, pinCode: string): Promise<Transaction> { | |||
async getSignatureForTx(tx: Transaction, storage: IStorage, pinCode: string): Promise<IInputSignature[]> { |
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.
note: This new util is the default signing method for the wallet-lib for now.
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.
thought(blocking): It troubles me that we're changing a central protection ( read only guards ) and removing the tests that would assure us it's working as intended later.
I'd suggest splitting this feature differently:
- First with a refactor for the transaction utils with all new signature methods. All tests would have to be green still
- Then adding a flag to allow for a full wallet with external cryptography ( the scope of feat: allow disabling the xpub guard #619 , as I understand ). The tests could be adapted accordingly, with almost no new tests needed.
- Lastly, implement the feature to actually initialize the wallet without any
xPriv
and having an external signing method. New tests would be made for this new feature.
What do you think of this approach?
@@ -515,7 +522,7 @@ class HathorWallet extends EventEmitter { | |||
sigs.push(hexToBuffer(p2shSig.signatures[index])); | |||
} catch (e) { | |||
// skip if there is no signature, or if it's not hex | |||
continue | |||
continue; |
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.
note(non-blocking): Codecov is raising one case that was not tested: having invalid p2sh signatures.
Since P2SH transactions involve message exchange and direct user input I think it's something actually plausible to happen. Maybe we could open an issue to remind us of this test case.
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.
Can you create an issue for this?
I think its important, but it is beyond the scope of this PR.
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.
Opened #623 requesting this test
* @param {EcdsaTxSign} txSign The signing function | ||
*/ | ||
setTxSign(txSign: EcdsaTxSign): void { | ||
this.txSignFunc = txSign; |
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.
question: Would it be too much of an effort to create a txSignFunc
and test it? The best case would be on an integration test, but I believe just a unit test would be enough for now.
That way we would ensure the override process is actually working.
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.
getSignatureForTx
is the perfect example, it fetches/derives the mainKey and uses the storage to iterate on the inputs that are owned by the wallet, signing only those that belong to the wallet.
Any other test method would essentially need to have to do the same.
During tests what I would do is create a function that prints something to stdout and call getSignatureForTx
, so I would know it called the correct method and that it works as expected.
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 if I understand: getSignatureForTx
is an example of signing function - actually the default one used by the lib, right?
CoveCov is informing that we have not tested this line 92
. As I understand, to increase this coverage in unit tests we would just need to call setTxSign
with a mocked function and ensure it's actually invoked whenever a signature is required within the scope of this storage/wallet.
Integration tests for an actual valid external signature could be left to another moment.
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.
Created an integration test showcasing this.
It will also increase the coverage since it tests the custom signature method case.
* @returns {Promise<string>} The public key DER encoded in hex | ||
*/ | ||
async getAddressPubkey(index: number): Promise<string> { | ||
const addressInfo = await this.store.getAddressAtIndex(index); |
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.
chore: We have no coverage on the new getAddressPubkey
method. I feel this is important to validate the new feature.
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.
Added tests
src/storage/storage.ts
Outdated
* @param {string} pinCode The pin code | ||
* @returns {Promise<IInputSignature[]>} The signatures | ||
*/ | ||
async signTx(tx: Transaction, pinCode: string): Promise<IInputSignature[]> { |
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.
suggestion: This method does not sign the transaction, but only returns an array of signatures for another method to inject it into the Transaction
object.
I'd suggest renaming this method to getTxSignatures(tx, pinCode)
to better reflect that it is a pure function.
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.
Done
df0e80d
to
dd9d4dd
Compare
Acceptance Criteria
transactionUtils.signTransaction
, our main signature method.Security Checklist