First iteration of account creation. More refactoring to come

This commit is contained in:
Matthew Aaron Raymer
2022-12-09 18:23:53 +08:00
parent cbad2e7308
commit b58c0ce820
7 changed files with 467 additions and 58 deletions

65
src/libs/crypto/index.ts Normal file
View File

@@ -0,0 +1,65 @@
import { IIdentifier } from "@veramo/core";
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
import { getRandomBytesSync } from "ethereum-cryptography/random";
import { entropyToMnemonic } from "ethereum-cryptography/bip39";
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
import { HDNode } from "@ethersproject/hdnode";
/**
*
*
* @param {string} address
* @param {string} publicHex
* @param {string} privateHex
* @param {string} derivationPath
* @return {*} {Omit<IIdentifier, 'provider'>}
*/
export const newIdentifier = (
address: string,
publicHex: string,
privateHex: string,
derivationPath: string
): Omit<IIdentifier, keyof "provider"> => {
return {
did: DEFAULT_DID_PROVIDER_NAME + ":" + address,
keys: [
{
kid: publicHex,
kms: "local",
meta: { derivationPath: derivationPath },
privateKeyHex: privateHex,
publicKeyHex: publicHex,
type: "Secp256k1",
},
],
provider: DEFAULT_DID_PROVIDER_NAME,
services: [],
};
};
export const deriveAddress = (
mnemonic: string
): [string, string, string, string] => {
const UPORT_ROOT_DERIVATION_PATH = "m/7696500'/0'/0'/0'";
mnemonic = mnemonic.trim().toLowerCase();
const hdnode: HDNode = HDNode.fromMnemonic(mnemonic);
const rootNode: HDNode = hdnode.derivePath(UPORT_ROOT_DERIVATION_PATH);
const privateHex = rootNode.privateKey.substring(2); // original starts with '0x'
const publicHex = rootNode.publicKey.substring(2); // original starts with '0x'
const address = rootNode.address;
return [address, privateHex, publicHex, UPORT_ROOT_DERIVATION_PATH];
};
/**
*
*
* @return {*} {string}
*/
export const createIdentifier = (): string => {
const entropy: Uint8Array = getRandomBytesSync(32);
const mnemonic = entropyToMnemonic(entropy, wordlist);
return mnemonic;
};

100
src/libs/veramo/appSlice.ts Normal file
View File

@@ -0,0 +1,100 @@
/* import * as R from "ramda";
import { configureStore, createSlice } from "@reduxjs/toolkit";
import { IIdentifier } from "@veramo/core";
import { Contact } from "../entity/contact";
import { Settings } from "../entity/settings";
import * as utility from "../utility/utility";
const MAX_LOG_LENGTH = 2000000;
export const DEFAULT_ENDORSER_API_SERVER = "https://endorser.ch:3000";
export const DEFAULT_ENDORSER_VIEW_SERVER = "https://endorser.ch";
export const LOCAL_ENDORSER_API_SERVER = "http://127.0.0.1:3000";
export const LOCAL_ENDORSER_VIEW_SERVER = "http://127.0.0.1:3001";
export const TEST_ENDORSER_API_SERVER = "https://test.endorser.ch:8000";
export const TEST_ENDORSER_VIEW_SERVER = "https://test.endorser.ch:8080";
// for contents set in reducers
interface Payload<T> {
type: string;
payload: T;
}
interface LogMsg {
log: boolean;
msg: string;
}
export const appSlice = createSlice({
name: "app",
initialState: {
// This is nullable because it is cached state from the DB...
// it'll be null if we haven't even loaded from the DB yet.
settings: null as Settings,
// This is nullable because it is cached state from the DB...
// it'll be null if we haven't even loaded from the DB yet.
identifiers: null as Array<IIdentifier> | null,
// This is nullable because it is cached state from the DB...
// it'll be null if we haven't even loaded from the DB yet.
contacts: null as Array<Contact> | null,
viewServer: DEFAULT_ENDORSER_VIEW_SERVER,
logMessage: "",
advancedMode: false,
testMode: false,
},
reducers: {
addIdentifier: (state, contents: Payload<IIdentifier>) => {
state.identifiers = state.identifiers.concat([contents.payload]);
},
addLog: (state, contents: Payload<LogMsg>) => {
if (state.logMessage.length > MAX_LOG_LENGTH) {
state.logMessage =
"<truncated>\n..." +
state.logMessage.substring(
state.logMessage.length - MAX_LOG_LENGTH / 2
);
}
if (contents.payload.log) {
console.log(contents.payload.msg);
state.logMessage += "\n" + contents.payload.msg;
}
},
setAdvancedMode: (state, contents: Payload<boolean>) => {
state.advancedMode = contents.payload;
},
setContacts: (state, contents: Payload<Array<Contact>>) => {
state.contacts = contents.payload;
},
setContact: (state, contents: Payload<Contact>) => {
const index = R.findIndex(
(c) => c.did === contents.payload.did,
state.contacts
);
state.contacts[index] = contents.payload;
},
setHomeScreen: (state, contents: Payload<string>) => {
state.settings.homeScreen = contents.payload;
},
setIdentifiers: (state, contents: Payload<Array<IIdentifier>>) => {
state.identifiers = contents.payload;
},
setSettings: (state, contents: Payload<Settings>) => {
state.settings = contents.payload;
},
setTestMode: (state, contents: Payload<boolean>) => {
state.testMode = contents.payload;
},
setViewServer: (state, contents: Payload<string>) => {
state.viewServer = contents.payload;
},
},
});
export const appStore = configureStore({ reducer: appSlice.reducer });
*/

151
src/libs/veramo/setup.ts Normal file
View File

@@ -0,0 +1,151 @@
// Created from the setup in https://veramo.io/docs/guides/react_native
// Core interfaces
/* import {
createAgent,
IDIDManager,
IResolver,
IDataStore,
IKeyManager,
} from "@veramo/core";
*/
// Core identity manager plugin
//import { DIDManager } from "@veramo/did-manager";
// Ethr did identity provider
//import { EthrDIDProvider } from "@veramo/did-provider-ethr";
// Core key manager plugin
//import { KeyManager } from "@veramo/key-manager";
// Custom key management system for RN
//import { KeyManagementSystem } from '@veramo/kms-local-react-native'
// Custom resolver
// Custom resolvers
//import { DIDResolverPlugin } from "@veramo/did-resolver";
/* import { Resolver } from "did-resolver";
import { getResolver as ethrDidResolver } from "ethr-did-resolver";
import { getResolver as webDidResolver } from "web-did-resolver";
*/
// for VCs and VPs https://veramo.io/docs/api/credential-w3c
//import { CredentialIssuer } from '@veramo/credential-w3c'
// Storage plugin using TypeOrm
/* import {
Entities,
KeyStore,
DIDStore,
IDataStoreORM,
} from "@veramo/data-store";
*/
// TypeORM is installed with @veramo/typeorm
//import { createConnection } from 'typeorm'
//import * as R from "ramda";
/*
import { Contact } from '../entity/contact'
import { Settings } from '../entity/settings'
import { PrivateData } from '../entity/privateData'
import { Initial1616938713828 } from '../migration/1616938713828-initial'
import { SettingsContacts1616967972293 } from '../migration/1616967972293-settings-contacts'
import { EncryptedSeed1637856484788 } from '../migration/1637856484788-EncryptedSeed'
import { HomeScreenConfig1639947962124 } from '../migration/1639947962124-HomeScreenConfig'
import { HandlePublicKeys1652142819353 } from '../migration/1652142819353-HandlePublicKeys'
import { LastClaimsSeen1656811846836 } from '../migration/1656811846836-LastClaimsSeen'
import { ContactRegistered1662256903367 }from '../migration/1662256903367-ContactRegistered'
import { PrivateData1663080623479 } from '../migration/1663080623479-PrivateData'
const ALL_ENTITIES = Entities.concat([Contact, Settings, PrivateData])
// Create react native DB connection configured by ormconfig.js
export const dbConnection = createConnection({
database: 'endorser-mobile.sqlite',
entities: ALL_ENTITIES,
location: 'default',
logging: ['error', 'info', 'warn'],
migrations: [ Initial1616938713828, SettingsContacts1616967972293, EncryptedSeed1637856484788, HomeScreenConfig1639947962124, HandlePublicKeys1652142819353, LastClaimsSeen1656811846836, ContactRegistered1662256903367, PrivateData1663080623479 ],
migrationsRun: true,
type: 'react-native',
})
*/
function didProviderName(netName: string) {
return "did:ethr" + (netName === "mainnet" ? "" : ":" + netName);
}
//const NETWORK_NAMES = ["mainnet", "rinkeby"];
const DEFAULT_DID_PROVIDER_NETWORK_NAME = "mainnet";
export const DEFAULT_DID_PROVIDER_NAME = didProviderName(
DEFAULT_DID_PROVIDER_NETWORK_NAME
);
export const HANDY_APP = false;
// this is used as the object in RegisterAction claims
export const SERVICE_ID = "endorser.ch";
//const INFURA_PROJECT_ID = "INFURA_PROJECT_ID";
/*
const providers = {}
NETWORK_NAMES.forEach((networkName) => {
providers[didProviderName(networkName)] = new EthrDIDProvider({
defaultKms: 'local',
network: networkName,
rpcUrl: 'https://' + networkName + '.infura.io/v3/' + INFURA_PROJECT_ID,
gas: 1000001,
ttl: 60 * 60 * 24 * 30 * 12 + 1,
})
})
const didManager = new DIDManager({
store: new DIDStore(dbConnection),
defaultProvider: DEFAULT_DID_PROVIDER_NAME,
providers: providers,
})
*/
/* const basicDidResolvers = NETWORK_NAMES.map((networkName) => [
networkName,
new Resolver({
ethr: ethrDidResolver({
networks: [
{
name: networkName,
rpcUrl:
"https://" + networkName + ".infura.io/v3/" + INFURA_PROJECT_ID,
},
],
}).ethr,
web: webDidResolver().web,
}),
]);
const basicResolverMap = R.fromPairs(basicDidResolvers)
export const DEFAULT_BASIC_RESOLVER = basicResolverMap[DEFAULT_DID_PROVIDER_NETWORK_NAME]
const agentDidResolvers = NETWORK_NAMES.map((networkName) => {
return new DIDResolverPlugin({
resolver: basicResolverMap[networkName],
})
})
let allPlugins = [
new CredentialIssuer(),
new KeyManager({
store: new KeyStore(dbConnection),
kms: {
local: new KeyManagementSystem(),
},
}),
didManager,
].concat(agentDidResolvers)
*/
//export const agent = createAgent<IDIDManager & IKeyManager & IDataStore & IDataStoreORM & IResolver>({ plugins: allPlugins })

View File

@@ -167,77 +167,62 @@
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { getRandomBytesSync } from "ethereum-cryptography/random";
import { entropyToMnemonic } from "ethereum-cryptography/bip39";
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
import { HDNode } from "@ethersproject/hdnode";
import { useAppStore } from "../store/app";
import { useAccountStore } from "../store/account";
import { createIdentifier, deriveAddress, newIdentifier } from "../libs/crypto";
import { IIdentifier } from "@veramo/core";
import * as R from "ramda";
import R from "ramda";
@Options({
components: {},
})
export default class AccountViewView extends Vue {
created() {
console.log("Component has been created!");
const entropy = getRandomBytesSync(32);
const mnemonic = entropyToMnemonic(entropy, wordlist);
const mnemonicPassword = "HardCodedPassword";
console.log(entropy, mnemonic);
this.importAndStoreIdentifier(mnemonic, mnemonicPassword, false, []);
}
importAndStoreIdentifier(
mnemonic: string,
mnemonicPassword: string,
toLowercase: boolean,
previousIdentifiers: Array<IIdentifier>
) {
const UPORT_ROOT_DERIVATION_PATH = "m/7696500'/0'/0'/0'";
mnemonic = mnemonic.trim().toLowerCase();
console.log(mnemonic);
const hdnode: HDNode = HDNode.fromMnemonic(mnemonic);
const rootNode: HDNode = hdnode.derivePath(UPORT_ROOT_DERIVATION_PATH);
const privateHex = rootNode.privateKey.substring(2); // original starts with '0x'
const publicHex = rootNode.publicKey.substring(2); // original starts with '0x'
let address = rootNode.address;
const previousIdentifiers: Array<IIdentifier> = [];
const toLowercase = true;
const appStore = useAppStore();
const accountStore = useAccountStore();
console.log(address, privateHex, publicHex);
if (appStore._condition == "uninitialized") {
const mnemonic = createIdentifier();
const prevIds = previousIdentifiers || [];
if (toLowercase) {
const foundEqual = R.find(
(id: IIdentifier) => id.did.split(":")[2] === address,
prevIds
);
if (foundEqual) {
// They're trying to create a lowercase version of one that exists in normal case.
// (We really should notify the user.)
// appStore.dispatch(appSlice.actions.addLog({log: true, msg: "Will create a normal-case version of the DID since a regular version exists."}))
const [address, privateHex, publicHex, UPORT_ROOT_DERIVATION_PATH] =
deriveAddress(mnemonic);
//appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... derived keys and address..."}))
const prevIds = previousIdentifiers || [];
let addr = address;
if (toLowercase) {
const foundEqual = R.find(
(id: IIdentifier) => id.did.split(":")[2] === address,
prevIds
);
if (foundEqual) {
// appStore.dispatch(appSlice.actions.addLog({log: true, msg: "Will create a normal-case version of the DID since a regular version exists."}))
} else {
addr = address.toLowerCase();
}
} else {
address = address.toLowerCase();
// They're not trying to convert to lowercase.
const foundLower = R.find(
(id: IIdentifier) => id.did.split(":")[2] === address.toLowerCase(),
prevIds
);
if (foundLower) {
// appStore.dispatch(appSlice.actions.addLog({log: true, msg: "Will create a lowercase version of the DID since a lowercase version exists."}))
addr = address.toLowerCase();
}
}
} else {
// They're not trying to convert to lowercase.
const foundLower = R.find(
(id: IIdentifier) => id.did.split(":")[2] === address.toLowerCase(),
prevIds
const newId = newIdentifier(
addr,
publicHex,
privateHex,
UPORT_ROOT_DERIVATION_PATH
);
if (foundLower) {
// They're trying to create a normal case version of one that exists in lowercase.
// (We really should notify the user.)
//appStore.dispatch(appSlice.actions.addLog({log: true, msg: "Will create a lowercase version of the DID since a lowercase version exists."}))
address = address.toLowerCase();
}
//appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... created new ID..."}))
accountStore.account = JSON.stringify(newId);
//appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... stored new ID..."}))
}
//appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... derived keys and address..."}))
//const newId = newIdentifier(address, publicHex, privateHex, UPORT_ROOT_DERIVATION_PATH)
//appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... created new ID..."}))
// awaiting because otherwise the UI may not see that a mnemonic was created
//const savedId = await storeIdentifier(newId, mnemonic, mnemonicPassword)
//appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... stored new ID..."}))
//return savedId;
}
}
</script>