Skip to content

Commit

Permalink
refactor: update account service to support sub accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
jackson-dean committed Sep 20, 2023
1 parent d78182a commit d837316
Show file tree
Hide file tree
Showing 12 changed files with 572 additions and 161 deletions.
439 changes: 357 additions & 82 deletions src/app/account.service.ts

Large diffs are not rendered by default.

18 changes: 5 additions & 13 deletions src/app/approve/approve.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ export class ApproveComponent implements OnInit {
}

onSubmit(): void {
const user = this.accountService.getEncryptedUsers()[this.publicKey];
const isDerived = this.accountService.isMetamaskAccount(user);
const account = this.accountService.getAccountInfo(this.publicKey);
const isDerived = this.accountService.isMetamaskAccount(account);
const signedTransactionHex = this.signingService.signTransaction(
this.seedHex(),
account.seedHex,
this.transactionHex,
isDerived
isDerived,
account.accountNumber
);
this.finishFlow(signedTransactionHex);
}
Expand All @@ -117,15 +118,6 @@ export class ApproveComponent implements OnInit {
});
}

seedHex(): string {
const encryptedSeedHex =
this.accountService.getEncryptedUsers()[this.publicKey].encryptedSeedHex;
return this.cryptoService.decryptSeedHex(
encryptedSeedHex,
this.globalVars.hostname
);
}

generateTransactionDescription(): void {
let description = 'sign an unknown transaction';
let publicKeys: string[] = [];
Expand Down
10 changes: 8 additions & 2 deletions src/app/auth/google/google.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ export class GoogleComponent implements OnInit {
mnemonic,
extraText,
network,
true
0,
{
google: true,
}
);
} catch (err) {
console.error(err);
Expand Down Expand Up @@ -146,7 +149,10 @@ export class GoogleComponent implements OnInit {
mnemonic,
extraText,
network,
true
0,
{
google: true,
}
);
this.loading = false;
});
Expand Down
16 changes: 8 additions & 8 deletions src/app/backend-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,24 +291,24 @@ export class BackendAPIService {
}

jwtPost(path: string, publicKey: string, body: any): Observable<any> {
const publicUserInfo = this.accountService.getEncryptedUsers()[publicKey];
const account = this.accountService.getAccountInfo(publicKey);
// NOTE: there are some cases where derived user's were not being sent phone number
// verification texts due to missing public user info. This is to log how often
// this is happening.
logInteractionEvent('backend-api', 'jwt-post', {
hasPublicUserInfo: !!publicUserInfo,
hasPublicUserInfo: !!account,
});

if (!publicUserInfo) {
if (!account) {
return of(null);
}
const isDerived = this.accountService.isMetamaskAccount(publicUserInfo);
const isDerived = this.accountService.isMetamaskAccount(account);

const seedHex = this.cryptoService.decryptSeedHex(
publicUserInfo.encryptedSeedHex,
this.globalVars.hostname
const jwt = this.signingService.signJWT(
account.seedHex,
account.accountNumber,
isDerived
);
const jwt = this.signingService.signJWT(seedHex, isDerived);
return this.post(path, { ...body, ...{ JWT: jwt } });
}

Expand Down
46 changes: 41 additions & 5 deletions src/app/crypto.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,25 +142,49 @@ export class CryptoService {
nonStandard?: boolean
): HDNode {
const seed = bip39.mnemonicToSeedSync(mnemonic, extraText);
// @ts-ignore
return HDKey.fromMasterSeed(seed).derive("m/44'/0'/0'/0/0", nonStandard);
return deriveKeys(seed, 0, {
nonStandard
});
}

getSubAccountKeychain(masterSeedHex: string, accountIndex: number): HDNode {
const seedBytes = Buffer.from(masterSeedHex, 'hex');
return deriveKeys(seedBytes, accountIndex);
}

keychainToSeedHex(keychain: HDNode): string {
return keychain.privateKey.toString('hex');
}

seedHexToPrivateKey(seedHex: string): EC.KeyPair {
/**
* For a given parent seed hex and account number, return the corresponding private key. Public/private
* key pairs are independent and unique based on a combination of the seed hex and account number.
* @param parentSeedHex This is the seed hex used to generate multiple HD wallets/keys from a single seed.
* @param accountNumber This is the account number used to generate unique keys from the parent seed.
* @returns
*/
seedHexToKeyPair(parentSeedHex: string, accountNumber: number): EC.KeyPair {
const ec = new EC('secp256k1');

if (accountNumber === 0) {
return ec.keyFromPrivate(parentSeedHex);
}

const hdKeys = this.getSubAccountKeychain(
parentSeedHex,
accountNumber
);
const seedHex = this.keychainToSeedHex(hdKeys);

return ec.keyFromPrivate(seedHex);
}

encryptedSeedHexToPublicKey(encryptedSeedHex: string): string {
encryptedSeedHexToPublicKey(encryptedSeedHex: string, accountNumber: number): string {
const seedHex = this.decryptSeedHex(
encryptedSeedHex,
this.globalVars.hostname
);
const privateKey = this.seedHexToPrivateKey(seedHex);
const privateKey = this.seedHexToKeyPair(seedHex, accountNumber);
return this.privateKeyToDeSoPublicKey(privateKey, this.globalVars.network);
}

Expand Down Expand Up @@ -279,3 +303,15 @@ export class CryptoService {
return ethAddressChecksum;
}
}

/**
* We set the account according to the following derivation path scheme:
* m / purpose' / coin_type' / account' / change / address_index
* See for more details: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account
*/
function deriveKeys(seedBytes: Buffer, accountIndex: number, options?: { nonStandard?: boolean }) {
// We are using a customized version of hdkey and the derive signature types
// are not compatible with the "nonStandard" flag. Hence the ts-ignore.
// @ts-ignore
return HDKey.fromMasterSeed(seedBytes).derive(`m/44'/0'/${accountIndex}'/0/0`, !!options?.nonStandard);
}
46 changes: 33 additions & 13 deletions src/app/identity.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,19 @@ export class IdentityService {

const {
id,
payload: { encryptedSeedHex, unsignedHashes },
payload: { encryptedSeedHex, unsignedHashes, ownerPublicKeyBase58Check },
} = data;
const seedHex = this.cryptoService.decryptSeedHex(
encryptedSeedHex,
this.globalVars.hostname
);
const { accountNumber = 0 } = ownerPublicKeyBase58Check
? this.accountService.getAccountInfo(ownerPublicKeyBase58Check)
: {};
const signedHashes = this.signingService.signHashes(
seedHex,
unsignedHashes
unsignedHashes,
accountNumber
);

this.respond(id, {
Expand All @@ -246,15 +250,19 @@ export class IdentityService {

const {
id,
payload: { encryptedSeedHex, unsignedHashes },
payload: { encryptedSeedHex, unsignedHashes, ownerPublicKeyBase58Check },
} = data;
const seedHex = this.cryptoService.decryptSeedHex(
encryptedSeedHex,
this.globalVars.hostname
);
const { accountNumber = 0 } = ownerPublicKeyBase58Check
? this.accountService.getAccountInfo(ownerPublicKeyBase58Check)
: {};
const signatures = this.signingService.signHashesETH(
seedHex,
unsignedHashes
unsignedHashes,
accountNumber
);

this.respond(id, {
Expand All @@ -269,6 +277,7 @@ export class IdentityService {
encryptedSeedHex,
transactionHex,
derivedPublicKeyBase58Check,
ownerPublicKeyBase58Check,
},
} = data;

Expand Down Expand Up @@ -296,13 +305,15 @@ export class IdentityService {
encryptedSeedHex,
this.globalVars.hostname
);

const isDerived = !!derivedPublicKeyBase58Check;

const { accountNumber = 0 } = ownerPublicKeyBase58Check
? this.accountService.getAccountInfo(ownerPublicKeyBase58Check)
: {};
const signedTransactionHex = this.signingService.signTransaction(
seedHex,
transactionHex,
isDerived
isDerived,
accountNumber
);

this.respond(id, {
Expand Down Expand Up @@ -362,7 +373,10 @@ export class IdentityService {
senderGroupKeyName,
recipientPublicKey,
message,
messagingKeyRandomness
{
ownerPublicKeyBase58Check,
messagingKeyRandomness,
}
);

this.respond(id, { ...encryptedMessage });
Expand Down Expand Up @@ -408,7 +422,8 @@ export class IdentityService {
try {
const decryptedHexes = this.accountService.decryptMessagesLegacy(
seedHex,
encryptedHexes
encryptedHexes,
data.payload.ownerPublicKeyBase58Check
);
this.respond(id, {
decryptedHexes,
Expand All @@ -426,8 +441,10 @@ export class IdentityService {
seedHex,
encryptedMessages,
data.payload.messagingGroups || [],
messagingKeyRandomness,
data.payload.ownerPublicKeyBase58Check
{
messagingKeyRandomness,
ownerPublicKeyBase58Check: data.payload.ownerPublicKeyBase58Check,
}
)
.then(
(res) => this.respond(id, { decryptedHexes: res }),
Expand All @@ -446,14 +463,17 @@ export class IdentityService {

const {
id,
payload: { encryptedSeedHex, derivedPublicKeyBase58Check },
payload: { encryptedSeedHex, derivedPublicKeyBase58Check, ownerPublicKeyBase58Check },
} = data;
const seedHex = this.cryptoService.decryptSeedHex(
encryptedSeedHex,
this.globalVars.hostname
);
const { accountNumber = 0 } = ownerPublicKeyBase58Check
? this.accountService.getAccountInfo(ownerPublicKeyBase58Check)
: {};
const isDerived = !!derivedPublicKeyBase58Check;
const jwt = this.signingService.signJWT(seedHex, isDerived);
const jwt = this.signingService.signJWT(seedHex, accountNumber, isDerived);

this.respond(id, {
jwt,
Expand Down
49 changes: 24 additions & 25 deletions src/app/log-in-seed/log-in-seed.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,41 +85,40 @@ export class LogInSeedComponent implements OnInit {
keychain,
mnemonic,
extraText,
network
network,
0
);

// NOTE: Temporary support for 1 in 128 legacy users who have non-standard derivations
if (keychain.publicKey !== keychainNonStandard.publicKey) {
const seedHex =
this.cryptoService.keychainToSeedHex(keychainNonStandard);
const privateKey = this.cryptoService.seedHexToPrivateKey(seedHex);
const seedHex = this.cryptoService.keychainToSeedHex(keychainNonStandard);
const privateKey = this.cryptoService.seedHexToKeyPair(seedHex, 0);
const publicKey = this.cryptoService.privateKeyToDeSoPublicKey(
privateKey,
network
);

// We only want to add nonStandard derivations if the account is worth importing
this.backendApi
.GetUsersStateless([publicKey], true, true)
.subscribe((res) => {
if (!res.UserList.length) {
return;
}
const user = res.UserList[0];
if (
user.ProfileEntryResponse ||
user.BalanceNanos > 0 ||
user.UsersYouHODL?.length
) {
// Add the non-standard key if the user has a profile, a balance, or holdings
userPublicKey = this.accountService.addUser(
keychainNonStandard,
mnemonic,
extraText,
network
);
}
});
this.backendApi.GetUsersStateless([publicKey]).subscribe((res) => {
if (!res.UserList.length) {
return;
}
const user = res.UserList[0];
if (
user.ProfileEntryResponse ||
user.BalanceNanos > 0 ||
user.UsersYouHODL?.length
) {
// Add the non-standard key if the user has a profile, a balance, or holdings
userPublicKey = this.accountService.addUser(
keychainNonStandard,
mnemonic,
extraText,
network,
0
);
}
});
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/app/sign-up-metamask/sign-up-metamask.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ export class SignUpMetamaskComponent implements OnInit {
const signedTransactionHex = this.signingService.signTransaction(
derivedKeyPair.getPrivate().toString('hex'),
authorizeDerivedKeyResponse.TransactionHex,
true
true,
0
);

this.backendApi
Expand Down
3 changes: 2 additions & 1 deletion src/app/sign-up/sign-up.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ export class SignUpComponent implements OnInit, OnDestroy {
keychain,
mnemonic,
extraText,
network
network,
0
);

this.accountService.setAccessLevel(
Expand Down
Loading

0 comments on commit d837316

Please sign in to comment.