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 {Injectable} from '@angular/core';
import {CryptoService} from './crypto.service'; import {CryptoService} from './crypto.service';
import {GlobalVarsService} from './global-vars.service'; import {GlobalVarsService} from './global-vars.service';
import {AccessLevel, Network, PrivateUserInfo, PublicUserInfo} from '../types/identity'; import {AccessLevel, PrivateAccountInfo, PublicAccountInfo} from '../types/identity';
import HDKey from 'hdkey';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AccountService { export class AccountService {
private static usersStorageKey = 'users';
private static levelsStorageKey = 'levels'; private static levelsStorageKey = 'levels';
private static publicKeyRegex = /^[a-zA-Z0-9]{54,55}$/;
constructor( constructor(
private cryptoService: CryptoService, private cryptoService: CryptoService,
private globalVars: GlobalVarsService, private globalVars: GlobalVarsService,
@ -20,41 +16,70 @@ export class AccountService {
// Public Getters // Public Getters
getPublicKeys(): any { getAccountNames(): any {
return Object.keys(this.getPrivateUsers()); // 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 hostname = this.globalVars.hostname;
const privateUsers = this.getPrivateUsers(); const privateAccounts = this.getWalletAccounts();
const publicUsers: {[key: string]: PublicUserInfo} = {}; const publicAccounts: {[key: string]: PublicAccountInfo} = {};
for (const publicKey of Object.keys(privateUsers)) { for (const name of Object.keys(privateAccounts)) {
const privateUser = privateUsers[publicKey]; const privateAccount = privateAccounts[name];
const accessLevel = this.getAccessLevel(publicKey, hostname); const accessLevel = this.getAccessLevel(name, hostname);
if (accessLevel === AccessLevel.None) { if (accessLevel === AccessLevel.None) {
continue; continue;
} }
const encryptedSeedHex = this.cryptoService.encryptSeedHex(privateUser.seedHex, hostname); // TODO
const accessLevelHmac = this.cryptoService.accessLevelHmac(accessLevel, privateUser.seedHex); throw 'Implement the hmac properly'
publicUsers[publicKey] = { // TODO - why do we even have hmac if everything's in local storage anyway?
hasExtraText: privateUser.extraText?.length > 0, const accessLevelHmac = this.cryptoService.accessLevelHmac(accessLevel, privateAccount.seed);
encryptedSeedHex,
network: privateUser.network, publicAccounts[name] = {
name,
network: privateAccount.ledger,
accessLevel, accessLevel,
accessLevelHmac, 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 levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
const hostMapping = levels[hostname] || {}; const hostMapping = levels[hostname] || {};
const accessLevel = hostMapping[publicKey]; const accessLevel = hostMapping[accountName];
if (Object.values(AccessLevel).includes(accessLevel)) { if (Object.values(AccessLevel).includes(accessLevel)) {
return accessLevel; return accessLevel;
@ -65,78 +90,25 @@ export class AccountService {
// Public Modifiers // Public Modifiers
addUser(keychain: HDKey, mnemonic: string, extraText: string, network: Network): string { setAccessLevel(accountName: string, hostname: string, accessLevel: AccessLevel): void {
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 {
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}'); const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
levels[hostname] ||= {}; levels[hostname] ||= {};
levels[hostname][publicKey] = accessLevel; levels[hostname][accountName] = accessLevel;
localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels)); 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 levels[hostname] ||= {};
public addPrivateUser(userInfo: PrivateUserInfo): string { for (const accountName in levels[hostname]) {
const privateUsers = this.getPrivateUsersRaw(); levels[hostname][accountName] = AccessLevel.None;
const privateKey = this.cryptoService.seedHexToPrivateKey(userInfo.seedHex);
const publicKey = this.cryptoService.privateKeyToDeSoPublicKey(privateKey, userInfo.network);
privateUsers[publicKey] = userInfo;
this.setPrivateUsersRaw(privateUsers);
return publicKey;
} }
private getPrivateUsers(): {[key: string]: PrivateUserInfo} { localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels));
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;
} }
// 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')) { 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. // 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 { finishFlow(signedTransactionHex?: string): void {
this.identityService.login({ this.identityService.login({
users: this.accountService.getEncryptedUsers(), accounts: this.accountService.getPublicAccounts(),
signedTransactionHex, signedTransactionHex,
}); });
} }

View file

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

View file

@ -1,7 +1,7 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Observable, Subject} from 'rxjs'; import {Observable, Subject} from 'rxjs';
import {v4 as uuid} from 'uuid'; import {v4 as uuid} from 'uuid';
import {AccessLevel, PublicUserInfo} from '../types/identity'; import {AccessLevel, PublicAccountInfo} from '../types/identity';
import {CryptoService} from './crypto.service'; import {CryptoService} from './crypto.service';
import {GlobalVarsService} from './global-vars.service'; import {GlobalVarsService} from './global-vars.service';
import {CookieService} from 'ngx-cookie'; import {CookieService} from 'ngx-cookie';
@ -65,8 +65,8 @@ export class IdentityService {
} }
login(payload: { login(payload: {
users: {[key: string]: PublicUserInfo}, accounts: {[key: string]: PublicAccountInfo}, // Channel ids
publicKeyAdded?: string, 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 signedUp?: boolean
signedTransactionHex?: string, signedTransactionHex?: string,
addresses?: string[], addresses?: string[],

View file

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

View file

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

View file

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

View file

@ -1,21 +1,47 @@
export interface PrivateUserInfo { // TODO: Use our wallet json-schema. Otherwise, bitcoinjs-lib probably already has a networks enum.
seedHex: string; // TODO: what about encrypted wallets?
mnemonic: string;
extraText: string;
network: Network;
}
export interface PublicUserInfo { export enum AddressType {
hasExtraText: boolean; DeterministicChain = "deterministic-chain",
encryptedSeedHex: string; SingleAddress = "single-address",
network: Network;
accessLevel: AccessLevel;
accessLevelHmac: string;
} }
export enum Network { export enum Network {
mainnet = 'mainnet', MainNet = "lbc_mainnet",
testnet = 'testnet', 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 { export enum AccessLevel {