Very WIP changes from DeSo structs to LBRY structs

This commit is contained in:
Daniel Krol 2022-05-04 15:56:44 -04:00
parent 6b6efe6ce0
commit a3023dc40c
10 changed files with 113 additions and 117 deletions

View file

@ -1,18 +1,14 @@
import {Injectable} from '@angular/core';
import {CryptoService} from './crypto.service';
import {GlobalVarsService} from './global-vars.service';
import {AccessLevel, Network, PrivateUserInfo, PublicUserInfo} from '../types/identity';
import HDKey from 'hdkey';
import {AccessLevel, PrivateAccountInfo, PublicAccountInfo} from '../types/identity';
@Injectable({
providedIn: 'root'
})
export class AccountService {
private static usersStorageKey = 'users';
private static levelsStorageKey = 'levels';
private static publicKeyRegex = /^[a-zA-Z0-9]{54,55}$/;
constructor(
private cryptoService: CryptoService,
private globalVars: GlobalVarsService,
@ -20,41 +16,70 @@ export class AccountService {
// Public Getters
getPublicKeys(): any {
return Object.keys(this.getPrivateUsers());
getAccountNames(): any {
// TODO - maybe write this in a safer, more future-perfect way since it's converting
// private to public
return Object.keys(this.getWalletAccounts());
}
getEncryptedUsers(): {[key: string]: PublicUserInfo} {
// TODO - As of this writing, we want to share channel claim ids with the
// account on login, and spending addresses on request (probably with
// explicit permission)
//
// This is in a state in between what DeSo had and what
// we want ultimately for LBRY.
getPublicAccounts(): {[key: string]: PublicAccountInfo} {
const hostname = this.globalVars.hostname;
const privateUsers = this.getPrivateUsers();
const publicUsers: {[key: string]: PublicUserInfo} = {};
const privateAccounts = this.getWalletAccounts();
const publicAccounts: {[key: string]: PublicAccountInfo} = {};
for (const publicKey of Object.keys(privateUsers)) {
const privateUser = privateUsers[publicKey];
const accessLevel = this.getAccessLevel(publicKey, hostname);
for (const name of Object.keys(privateAccounts)) {
const privateAccount = privateAccounts[name];
const accessLevel = this.getAccessLevel(name, hostname);
if (accessLevel === AccessLevel.None) {
continue;
}
const encryptedSeedHex = this.cryptoService.encryptSeedHex(privateUser.seedHex, hostname);
const accessLevelHmac = this.cryptoService.accessLevelHmac(accessLevel, privateUser.seedHex);
// TODO
throw 'Implement the hmac properly'
publicUsers[publicKey] = {
hasExtraText: privateUser.extraText?.length > 0,
encryptedSeedHex,
network: privateUser.network,
// TODO - why do we even have hmac if everything's in local storage anyway?
const accessLevelHmac = this.cryptoService.accessLevelHmac(accessLevel, privateAccount.seed);
publicAccounts[name] = {
name,
network: privateAccount.ledger,
accessLevel,
accessLevelHmac,
};
}
return publicUsers;
return publicAccounts;
}
getAccessLevel(publicKey: string, hostname: string): AccessLevel {
// TODO - Need to confirm that this works I think
public getWalletAccounts(): {[key: string]: PrivateAccountInfo} {
const wallet = this.cryptoService.getWallet(this.globalVars.hostname)
if (wallet === null) {
return {}
}
const filteredAccounts: {[key: string]: PrivateAccountInfo} = {};
for (const account of wallet.accounts) {
// Only include accounts from the current network
if (account.ledger !== this.globalVars.network) {
continue;
}
filteredAccounts[account.name] = account;
}
return filteredAccounts
}
getAccessLevel(accountName: string, hostname: string): AccessLevel {
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
const hostMapping = levels[hostname] || {};
const accessLevel = hostMapping[publicKey];
const accessLevel = hostMapping[accountName];
if (Object.values(AccessLevel).includes(accessLevel)) {
return accessLevel;
@ -65,78 +90,25 @@ export class AccountService {
// Public Modifiers
addUser(keychain: HDKey, mnemonic: string, extraText: string, network: Network): string {
const seedHex = this.cryptoService.keychainToSeedHex(keychain);
return this.addPrivateUser({
seedHex,
mnemonic,
extraText,
network,
});
}
deleteUser(publicKey: string): void {
const privateUsers = this.getPrivateUsersRaw();
delete privateUsers[publicKey];
this.setPrivateUsersRaw(privateUsers);
}
setAccessLevel(publicKey: string, hostname: string, accessLevel: AccessLevel): void {
setAccessLevel(accountName: string, hostname: string, accessLevel: AccessLevel): void {
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
levels[hostname] ||= {};
levels[hostname][publicKey] = accessLevel;
levels[hostname][accountName] = accessLevel;
localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels));
}
// Private Getters and Modifiers
// log out of hostname entirely by setting accesslevel to None
resetAccessLevels(hostname: string): void {
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
// TEMP: public for import flow
public addPrivateUser(userInfo: PrivateUserInfo): string {
const privateUsers = this.getPrivateUsersRaw();
const privateKey = this.cryptoService.seedHexToPrivateKey(userInfo.seedHex);
const publicKey = this.cryptoService.privateKeyToDeSoPublicKey(privateKey, userInfo.network);
privateUsers[publicKey] = userInfo;
this.setPrivateUsersRaw(privateUsers);
return publicKey;
levels[hostname] ||= {};
for (const accountName in levels[hostname]) {
levels[hostname][accountName] = AccessLevel.None;
}
private getPrivateUsers(): {[key: string]: PrivateUserInfo} {
const privateUsers = this.getPrivateUsersRaw();
const filteredPrivateUsers: {[key: string]: PrivateUserInfo} = {};
for (const publicKey of Object.keys(privateUsers)) {
const privateUser = privateUsers[publicKey];
// Only include users from the current network
if (privateUser.network !== this.globalVars.network) {
continue;
localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels));
}
// Get rid of some users who have invalid public keys
if (!publicKey.match(AccountService.publicKeyRegex)) {
this.deleteUser(publicKey);
continue;
}
filteredPrivateUsers[publicKey] = privateUser;
}
return filteredPrivateUsers;
}
private getPrivateUsersRaw(): {[key: string]: PrivateUserInfo} {
return JSON.parse(localStorage.getItem(AccountService.usersStorageKey) || '{}');
}
private setPrivateUsersRaw(privateUsers: {[key: string]: PrivateUserInfo}): void {
localStorage.setItem(AccountService.usersStorageKey, JSON.stringify(privateUsers));
}
}

View file

@ -32,7 +32,7 @@ export class AppComponent implements OnInit {
}
if (params.get('testnet')) {
this.globalVars.network = Network.testnet;
this.globalVars.network = Network.TestNet;
}
// Callback should only be used in mobile applications, where payload is passed through URL parameters.

View file

@ -84,7 +84,7 @@ export class ApproveComponent implements OnInit {
finishFlow(signedTransactionHex?: string): void {
this.identityService.login({
users: this.accountService.getEncryptedUsers(),
accounts: this.accountService.getPublicAccounts(),
signedTransactionHex,
});
}

View file

@ -6,7 +6,7 @@ import {environment} from '../environments/environment';
providedIn: 'root'
})
export class GlobalVarsService {
network = Network.mainnet;
network : Network = Network.MainNet;
hostname = '';
accessLevelRequest = AccessLevel.ApproveAll;

View file

@ -1,7 +1,7 @@
import {Injectable} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import {v4 as uuid} from 'uuid';
import {AccessLevel, PublicUserInfo} from '../types/identity';
import {AccessLevel, PublicAccountInfo} from '../types/identity';
import {CryptoService} from './crypto.service';
import {GlobalVarsService} from './global-vars.service';
import {CookieService} from 'ngx-cookie';
@ -65,8 +65,8 @@ export class IdentityService {
}
login(payload: {
users: {[key: string]: PublicUserInfo},
publicKeyAdded?: string,
accounts: {[key: string]: PublicAccountInfo}, // Channel ids
accountNameAdded?: string, // Which channel id was just authorized. the app may allow the user to switch between them, but this determines which is on now.
signedUp?: boolean
signedTransactionHex?: string,
addresses?: string[],

View file

@ -1,9 +1,7 @@
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {CryptoService} from '../crypto.service';
import {IdentityService} from '../identity.service';
import {AccountService} from '../account.service';
import {AccessLevel} from '../../types/identity';
import {GlobalVarsService} from '../global-vars.service';
@Component({
@ -20,7 +18,6 @@ export class LogoutComponent implements OnInit {
constructor(
private activatedRoute: ActivatedRoute,
private cryptoService: CryptoService,
private identityService: IdentityService,
private accountService: AccountService,
@ -28,9 +25,6 @@ export class LogoutComponent implements OnInit {
) { }
ngOnInit(): void {
this.activatedRoute.queryParams.subscribe(params => {
this.publicKey = params.publicKey || '';
});
}
onCancel(): void {
@ -39,7 +33,7 @@ export class LogoutComponent implements OnInit {
onSubmit(): void {
// We set the accessLevel for the logged out user to None.
this.accountService.setAccessLevel(this.publicKey, this.globalVars.hostname, AccessLevel.None);
this.accountService.resetAccessLevels(this.globalVars.hostname);
// We reset the seed encryption key so that all existing accounts, except
// the logged out user, will regenerate their encryptedSeedHex. Without this,
// someone could have reused the encryptedSeedHex of an already logged out user.
@ -49,7 +43,7 @@ export class LogoutComponent implements OnInit {
finishFlow(): void {
this.identityService.login({
users: this.accountService.getEncryptedUsers(),
accounts: this.accountService.getPublicAccounts(),
});
}

View file

@ -34,7 +34,7 @@ export class TestLbryLogInComponent implements OnInit {
finishFlow(addresses: string[]): void {
this.identityService.login({
users: {}, // TODO sigh
accounts: this.accountService.getPublicAccounts(),
addresses,
});
}

View file

@ -1,3 +1,4 @@
import {AccountService} from '../account.service';
import {Component, OnInit} from '@angular/core';
import {SigningService} from '../signing.service';
import {IdentityService} from '../identity.service';
@ -14,6 +15,7 @@ export class TestSignTransactionComponent implements OnInit {
signedTransactionHex?: string
constructor(
private accountService: AccountService,
private signingService: SigningService,
private identityService: IdentityService,
private cryptoService: CryptoService,
@ -39,7 +41,7 @@ export class TestSignTransactionComponent implements OnInit {
finishFlow(signedTransactionHex?: string): void {
this.identityService.login({
users: {}, // TODO sigh
accounts: this.accountService.getPublicAccounts(),
signedTransactionHex,
});
}

View file

@ -1,3 +1,4 @@
import {AccountService} from '../account.service';
import {Component, OnInit} from '@angular/core';
import {SigningService} from '../signing.service';
import {IdentityService} from '../identity.service';
@ -18,6 +19,7 @@ export class TestSignComponent implements OnInit {
privateKeyString: string = "thhUUVXQtyxonMaezCKwihLw9tZUrGJgWBMxDNfxoWub8dLGA"
constructor(
private accountService: AccountService,
private signingService: SigningService,
private identityService: IdentityService,
) { }
@ -34,7 +36,7 @@ export class TestSignComponent implements OnInit {
finishFlow(signatureHex?: string): void {
this.identityService.login({
users: {}, // TODO sigh
accounts: this.accountService.getPublicAccounts(),
signatureHex,
});
}

View file

@ -1,21 +1,47 @@
export interface PrivateUserInfo {
seedHex: string;
mnemonic: string;
extraText: string;
network: Network;
}
// TODO: Use our wallet json-schema. Otherwise, bitcoinjs-lib probably already has a networks enum.
// TODO: what about encrypted wallets?
export interface PublicUserInfo {
hasExtraText: boolean;
encryptedSeedHex: string;
network: Network;
accessLevel: AccessLevel;
accessLevelHmac: string;
export enum AddressType {
DeterministicChain = "deterministic-chain",
SingleAddress = "single-address",
}
export enum Network {
mainnet = 'mainnet',
testnet = 'testnet',
MainNet = "lbc_mainnet",
TestNet = "lbc_testnet",
RegTest = "lbc_regtest",
}
// Only safe for web wallet, not sent to the app
export interface PrivateAccountInfo {
address_generator: {
change?: {
gap: number,
maximum_uses_per_address: number,
},
name: AddressType,
receiving?: {
gap: number,
maximum_uses_per_address: number,
}
},
certificates: {[key: string]: string},
encrypted: boolean,
ledger: Network,
modified_on: number,
name: string,
private_key: string,
public_key: string,
seed: string,
}
// can be sent to the app
export interface PublicAccountInfo {
// TODO - add more useful stuff
name: string;
network: Network;
accessLevel: AccessLevel;
accessLevelHmac: string;
}
export enum AccessLevel {