Skip to content

Commit

Permalink
Merge pull request #255 from getsafle/feature-recover-vault-with-logs
Browse files Browse the repository at this point in the history
Implemented vault recovery using logs and updated test cases
  • Loading branch information
sshubhamagg authored Aug 29, 2023
2 parents 3b35cfc + 63ec1c5 commit 5d071ad
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 32 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,4 +534,5 @@
* Added validation for pin parameter in export private key, restore keyring state & current pin parameter in change pin
* Added validation for encryption key in add account, sign message, delete account, get vault details & update label
* Sync the pin validation steps with other methods in import wallet
* Updated tests wrt changes in vault generation and parameter validations
* Updated tests wrt changes in vault generation and parameter validations
* Implemented vault recovery using logs and updated tests
3 changes: 2 additions & 1 deletion src/constants/responses/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ module.exports = {
INCORRECT_CURRENT_PIN: 'The current pin passed is incorrect',
INCORRECT_LABEL_TYPE: 'Label should be a valid string',
REQUEST_LIMIT_EXCEEDED: 'Request limit exceeded. Please retry later',
REQUEST_BLOCKED: 'Requests to method blocked'
REQUEST_BLOCKED: 'Requests to method blocked',
INVALID_API_KEY: 'Invalid API key passed'
};
90 changes: 82 additions & 8 deletions src/lib/test/vault.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,70 @@ let phrase="fun rough treat scan glimpse region century purpose expire video rem
let pin=696969
let vault =new Vault({})

const logs = [
{
"action": "add-account",
"timestamp": 1000000000001,
"platform": "web",
"address": "0xF8919220F674a553F0F0F6e86481612A2bEd44EB",
"storage": [
"mobile"
],
"_id": "64e881b05b04774ca85aee51"
},
{
"action": "add-account",
"timestamp": 1000000000002,
"platform": "web",
"address": "0x627437E29e7363C0F53896e84467EF6F8f9D0247",
"storage": [
"mobile"
],
"_id": "64e881e3bae0e048dfaefc46"
},
{
"action": "add-account",
"timestamp": 1000000000003,
"platform": "web",
"address": "0xa1F77e4D8306000639D1d44a6013ad53b992182E",
"storage": [
"mobile"
],
"_id": "64ec3339a58abcbf66a9b34a"
},
{
"action": "add-account",
"timestamp": 1000000000004,
"platform": "web",
"address": "0x9e6627384a3E6453b9EC061e4DaeD4cE0223bbdc",
"storage": [
"mobile"
],
"_id": "64ec333ca58abcbf66a9b354"
},
{
"action": "add-account",
"timestamp": 1000000000005,
"platform": "mobile",
"address": "0xCccbD31ea19acE5688731148a4f63907F273BEe0",
"storage": [
"mobile"
],
"_id": "64e87e9e72e00ccf96bce1fc"
},
{
"action": "delete-account",
"timestamp": 1000000000006,
"platform": "web",
"address": "0x9e6627384a3E6453b9EC061e4DaeD4cE0223bbdc",
"storage": [
"mobile"
],
"_id": "64ec3339a58abcbf66a9b34a"
},
]


describe('getSupportedChains' , ()=>{

test('getSupportedChains' , async()=>{
Expand Down Expand Up @@ -105,11 +169,24 @@ describe("generateVault",()=>{


describe("recoverVault",()=>{
test('recoverVault/valid' , async()=>{
test('recoverVault/transaction valid' , async()=>{

let result = await vault.recoverVault(phrase,bufView,pin,'BgoGMHvB5R7iMNhZ2BoJd470aSZNEz9t2N8PBOWD')
expect(result).toHaveProperty('response')
})

test('recoverVault/logs valid' , async()=>{

let result = await vault.recoverVault(phrase,bufView,pin,null,'logs', logs)
expect(result).toHaveProperty('response')
})

test('recoverVault/logs empty logs valid' , async()=>{

let result = await vault.recoverVault(phrase,bufView,pin,null,'logs')
expect(result).toHaveProperty('response')
})

test('recoverVault/empty phrase' , async()=>{
try{
let result = await vault.recoverVault(null,bufView,pin,'BgoGMHvB5R7iMNhZ2BoJd470aSZNEz9t2N8PBOWD')
Expand Down Expand Up @@ -140,7 +217,8 @@ describe("recoverVault",()=>{
})
test('recoverVault/empty encryption key' , async()=>{
let result = await vault.recoverVault(phrase,null,pin,'BgoGMHvB5R7iMNhZ2BoJd470aSZNEz9t2N8PBOWD')
expect(result).toHaveProperty('response')
expect(result.error).toBe("Please enter both encryptionKey and pin")


})
test('recoverVault/invalid encryption key' , async()=>{
Expand All @@ -164,14 +242,10 @@ describe("recoverVault",()=>{

})
test('recoverVault/empty marshal key' , async()=>{
try{
let result = await vault.recoverVault(phrase,bufView,pin,null)

}
catch(e){
expect(e.message).toBe("Cannot destructure property 'transactions' of 'response' as it is undefined.")
let result = await vault.recoverVault(phrase,bufView,pin,null)
expect(result.error).toBe("Invalid API key passed")

}

})
test('recoverVault/invalid marshal key' , async()=>{
Expand Down
15 changes: 13 additions & 2 deletions src/lib/vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,21 +98,32 @@ class Vault extends Keyring {
return { response: vault };
}

async recoverVault(mnemonic, encryptionKey, pin, unmarshalApiKey) {
async recoverVault(mnemonic, encryptionKey, pin, unmarshalApiKey, recoverMechanism = 'transactions', logs = {}) {

if (!Number.isInteger(pin) || pin < 0 || pin.toString().length !=6) {
return { error: ERROR_MESSAGE.INCORRECT_PIN_TYPE };
}

if (!encryptionKey) {
return { error : ERROR_MESSAGE.ENTER_CREDS }
}

if(recoverMechanism === 'transactions' && !unmarshalApiKey) {
return { error: ERROR_MESSAGE.INVALID_API_KEY };
}

const vaultState = await this.keyringInstance.createNewVaultAndRestore(JSON.stringify(encryptionKey), mnemonic);

const accountsArray = await helper.removeEmptyAccounts(vaultState.keyrings[0].accounts[0], this.keyringInstance, vaultState, unmarshalApiKey);
const accountsArray = await helper.removeEmptyAccounts(vaultState.keyrings[0].accounts[0], this.keyringInstance, vaultState, unmarshalApiKey, recoverMechanism, logs);

const privData = await helper.generatePrivData(mnemonic, pin);

const numberOfAccounts = accountsArray.length;

const rawVault = { eth: { public: accountsArray, private: privData, numberOfAccounts } }

this.decryptedVault = rawVault

const vault = await helper.cryptography(JSON.stringify(rawVault), JSON.stringify(encryptionKey), 'encryption');

this.vault = vault;
Expand Down
59 changes: 39 additions & 20 deletions src/utils/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,54 @@ async function generatePrivData(mnemonic, pin) {
return priv;
}

async function removeEmptyAccounts(indexAddress, keyringInstance, vaultState, unmarshalApiKey) {

async function removeEmptyAccounts(indexAddress, keyringInstance, vaultState, unmarshalApiKey, recoverMechanism, logs) {
const keyring = keyringInstance.getKeyringsByType(vaultState.keyrings[0].type);

let zeroCounter = 0;
let accountsArray = [];
accountsArray.push({ address: indexAddress, isDeleted: false, isImported: false, label: 'Wallet 1' });

do {
zeroCounter = 0;
for(let i=0; i < 5; i++) {
const vaultState = await keyringInstance.addNewAccount(keyring[0]);

const ethActivity = await getETHTransactions(vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], 'ethereum', unmarshalApiKey);
const polygonActivity = await getPolygonTransactions(vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], 'polygon', unmarshalApiKey);
const bscActivity = await getBSCTransactions(vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], 'bsc', unmarshalApiKey);
const label = this.createWalletLabels('all', i+2);

if (!ethActivity && !polygonActivity && !bscActivity) {
accountsArray.push({ address: vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], isDeleted: true, isImported: false, label, isExported: false });
zeroCounter++;
} else {
accountsArray.push({ address: vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], isDeleted: false, isImported: false, label, isExported: false });
zeroCounter = 0;
if( recoverMechanism === 'logs'){
for(let i=0; i < logs.length; i++){
if (logs[i].action === 'add-account'){
const vaultState = await keyringInstance.addNewAccount(keyring[0]);
const newAccountAddr = Web3.utils.toChecksumAddress(vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1])
const label = this.createWalletLabels('all', i+2);
if (Web3.utils.toChecksumAddress(logs[i].address) === newAccountAddr) {
accountsArray.push({ address: newAccountAddr, isDeleted: false, isImported: false, label, isExported: false });
}

}
if(logs[i].action === 'delete-account') {
let ind = accountsArray.findIndex((acc) => acc.address === Web3.utils.toChecksumAddress(logs[i].address))
ind >= 0 ? accountsArray[ind].isDeleted = true : false;
}
}
}

while (zeroCounter < 5 )

} else if( recoverMechanism === 'transactions'){
do {
zeroCounter = 0;
for(let i=0; i < 5; i++) {
const vaultState = await keyringInstance.addNewAccount(keyring[0]);

const ethActivity = await getETHTransactions(vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], 'ethereum', unmarshalApiKey);
const polygonActivity = await getPolygonTransactions(vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], 'polygon', unmarshalApiKey);
const bscActivity = await getBSCTransactions(vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], 'bsc', unmarshalApiKey);
const label = this.createWalletLabels('all', i+2);

if (!ethActivity && !polygonActivity && !bscActivity) {
accountsArray.push({ address: vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], isDeleted: true, isImported: false, label, isExported: false });
zeroCounter++;
} else {
accountsArray.push({ address: vaultState.keyrings[0].accounts[vaultState.keyrings[0].accounts.length - 1], isDeleted: false, isImported: false, label, isExported: false });
zeroCounter = 0;
}
}
}

while (zeroCounter < 5 )
}
return accountsArray;
}

Expand Down

1 comment on commit 5d071ad

@github-actions
Copy link

Choose a reason for hiding this comment

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

Coverage Report (70%)
File% Stmts% Branch% Funcs% LinesUncovered Line #s
All files68.9961.2870.4270.1 
chains100100100100 
   index.js100100100100 
config100100100100 
   index.js100100100100 
constants100100100100 
   index.js100100100100 
constants/responses100100100100 
   index.js100100100100 
lib65.2261.1866.6666.59 
   keyring.js62.0859.6263.6363.5445, 131, 137–149, 169, 177–185, 194–196, 239–337, 356, 364–365, 373–383, 394, 414–425, 441–444, 476–480, 491–500, 534, 557–613, 645, 676, 684–700, 726–728, 759–767, 790–794, 813, 851
   vault.js85.93758085.9319, 26, 41–44, 56, 65–68
utils81.0362.1682.3581.48 
   helper.js81.0362.1682.3581.4810–17, 70–71, 87, 99, 111, 124–126, 142–144, 177–181, 222–224

Please sign in to comment.