Skip to content

Commit

Permalink
identity sign
Browse files Browse the repository at this point in the history
  • Loading branch information
backslash47 committed Apr 26, 2019
1 parent a5e26f0 commit 65676d8
Show file tree
Hide file tree
Showing 16 changed files with 274 additions and 72 deletions.
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "cyano-wallet",
"version": "0.7.10",
"version": "0.7.11",
"private": true,
"scripts": {
"lint": "tslint -p .",
Expand Down
2 changes: 1 addition & 1 deletion public/manifest.json
Expand Up @@ -4,7 +4,7 @@
"name": "Cyano wallet",
"author": "Matus Zamborsky <zamborsky@gmail.com>",
"description": "Cyano wallet - an Ontology wallet",
"version": "0.7.10",
"version": "0.7.11",

"browser_action": {
"default_title": "Open the wallet"
Expand Down
46 changes: 46 additions & 0 deletions src/api/identityApi.ts
Expand Up @@ -15,6 +15,7 @@
* You should have received a copy of the GNU Lesser General Public License
* along with The Ontology Wallet&ID. If not, see <http://www.gnu.org/licenses/>.
*/
import { get } from 'lodash';
import { Crypto, Identity, utils, Wallet } from 'ontology-ts-sdk';
import { v4 as uuid } from 'uuid';
import { deserializePrivateKey } from './accountApi';
Expand All @@ -34,6 +35,51 @@ export function decryptIdentity(identity: Identity, password: string, scrypt: an
});
}

export function decryptDefaultIdentity(wallet: Wallet, password: string, scrypt: any) {
const ontId = wallet.defaultOntid !== '' ? wallet.defaultOntid : null;

if (ontId === null) {
throw new Error('Default identity not found in wallet');
}

const identity = wallet.identities.find((i) => i.ontid === ontId);

if (identity === undefined) {
throw new Error(`Identity ${ontId} not found in the wallet.`);
}

return decryptIdentity(identity, password, scrypt);
}

export function hasIdentity(wallet: Wallet) {
return wallet.identities.length > 0;
}

export function getDefaultIdentity(wallet: Wallet) {
const ontId = wallet.defaultOntid !== '' ? wallet.defaultOntid : null;

if (ontId === null) {
if (wallet.identities.length > 0) {
return wallet.identities[0];
} else {
throw new Error('No identities found.');
}
}

const identity = wallet.identities.find((i) => i.ontid === ontId);

if (identity === undefined) {
throw new Error(`Identity ${ontId} not found in the wallet.`);
}

return identity;
}

export function isIdentityLedgerKey(wallet: Wallet) {
return get(getDefaultIdentity(wallet).controls[0].encryptedKey, 'type') === 'LEDGER';
}


export function identitySignUp(password: string, scrypt: any, neo: boolean) {
const mnemonics = utils.generateMnemonic(32);
return identityImportMnemonics(mnemonics, password, scrypt, neo);
Expand Down
82 changes: 61 additions & 21 deletions src/background/api/smartContractApi.ts
Expand Up @@ -16,7 +16,7 @@
* along with The Ontology Wallet&ID. If not, see <http://www.gnu.org/licenses/>.
*/
import { Parameter } from 'ontology-dapi';
import { Crypto, TransactionBuilder, utils } from 'ontology-ts-sdk';
import { Crypto, Transaction, TransactionBuilder, utils } from 'ontology-ts-sdk';
import { buildInvokePayload } from 'ontology-ts-test';
import { decryptAccount, getAccount } from '../../api/accountApi';
import { getWallet } from '../../api/authApi';
Expand All @@ -25,37 +25,77 @@ import { getClient } from '../network';
import { getStore } from '../redux';

import Address = Crypto.Address;

export async function scCall(request: ScCallRequest, password: string) {
import {decryptDefaultIdentity } from 'src/api/identityApi';

/**
* Creates, signs and sends the transaction for Smart Contract call.
*
* Can work in two modes:
* 1. simple account sign (requireIdentity = false)
* - the transaction is created, signed and sent in one step
* 2. account + identity sign (requireIdentity = true)
* - the transaction is created, signed by account and stored in first step (presignedTransaction = undefined)
* - signed by identity and sent in second step (presignedTransaction = serialized transaction)
*
* @param request request describing the SC call
* @param password password to decode the private key (account or identity)
*/
export async function scCall(request: ScCallRequest, password: string): Promise<string | any> {
request.parameters = request.parameters !== undefined ? request.parameters : [];
request.gasPrice = request.gasPrice !== undefined ? request.gasPrice : 500;
request.gasLimit = request.gasLimit !== undefined ? request.gasLimit : 30000;

const state = getStore().getState();
const wallet = getWallet(state.wallet.wallet!);

const account = getAccount(state.wallet.wallet!).address;
const privateKey = decryptAccount(wallet, password);

// convert params
const params = convertParams(request.parameters);
const payload = buildInvokePayload(request.contract, request.method, params);

let tx: Transaction;
if (request.presignedTransaction) {
tx = Transaction.deserialize(request.presignedTransaction);
} else {
// convert params
const params = convertParams(request.parameters);
const payload = buildInvokePayload(request.contract, request.method, params);

tx = TransactionBuilder.makeInvokeTransaction(
request.method,
[],
new Address(utils.reverseHex(request.contract)),
String(request.gasPrice),
String(request.gasLimit),
account,
);

(tx.payload as any).code = payload.toString('hex');
}

const tx = TransactionBuilder.makeInvokeTransaction(
request.method,
[],
new Address(utils.reverseHex(request.contract)),
String(request.gasPrice),
String(request.gasLimit),
account,
);
let privateKey: Crypto.PrivateKey;
if (request.requireIdentity && request.presignedTransaction) {
// mode 2, step 2:
// already signed by account
// do signature by identity
privateKey = decryptDefaultIdentity(wallet, password, wallet.scrypt);

(tx.payload as any).code = payload.toString('hex');
// fixme: add support for async sign
TransactionBuilder.addSign(tx, privateKey);
} else {
// mode 1 or mode 2, step 1:
// do signature by account
privateKey = decryptAccount(wallet, password);
await TransactionBuilder.signTransactionAsync(tx, privateKey);
}

await TransactionBuilder.signTransactionAsync(tx, privateKey);
if (request.requireIdentity && !request.presignedTransaction) {
// mode 2, step 1
// return the presigned transaction to be stored in request
// outside of this method
return tx.serialize();

const client = getClient();
return await client.sendRawTransaction(tx.serialize(), false, true);
} else {
// mode 1 or mode 2, step 2
const client = getClient();
return await client.sendRawTransaction(tx.serialize(), false, true);
}
}

export async function scCallRead(request: ScCallReadRequest) {
Expand Down
18 changes: 18 additions & 0 deletions src/background/popUpManager.ts
Expand Up @@ -17,6 +17,7 @@
*/
import { MethodType, Rpc } from 'ontology-dapi';
import { Identity } from 'ontology-ts-sdk';
import { decryptDefaultIdentity } from 'src/api/identityApi';
import { browser } from 'webextension-polyfill-ts';
import { decryptAccount } from '../api/accountApi';
import { getWallet } from '../api/authApi';
Expand Down Expand Up @@ -53,6 +54,7 @@ export class PopupManager {

this.rpc.register('popup_initialized', this.pupupInitialized.bind(this));
this.rpc.register('check_account_password', this.checkAccountPassword.bind(this));
this.rpc.register('check_identity_password', this.checkIdentityPassword.bind(this));
this.rpc.register('check_ont_id', this.checkOntId.bind(this));
this.rpc.register('get_oep4_token', this.getOEP4Token.bind(this));
this.rpc.register('is_ledger_supported', this.isLedgerSupported.bind(this));
Expand Down Expand Up @@ -124,6 +126,22 @@ export class PopupManager {
}
}

private checkIdentityPassword(password: string) {
const encodedWallet = this.store.getState().wallet.wallet;
if (encodedWallet === null) {
throw new Error('NO_IDENTITY');
}

try {
const wallet = getWallet(encodedWallet);
decryptDefaultIdentity(wallet, password, wallet.scrypt);

return true;
} catch (e) {
return false;
}
}

private checkOntId(identityEncoded: string, password: string) {
const identity = Identity.parseJson(identityEncoded);
return checkOntId(identity, password);
Expand Down
33 changes: 24 additions & 9 deletions src/background/redux/transactionRequestsReducer.ts
Expand Up @@ -108,6 +108,9 @@ export const transactionRequestsAliases = {
break;
case 'sc_call':
result = await submitScCall(request as ScCallRequest, password!, dispatch, state);
if (result === undefined) {
return;
}
break;
case 'sc_call_read':
result = await submitScCallRead(request as ScCallReadRequest);
Expand Down Expand Up @@ -186,6 +189,10 @@ async function submitRegisterOntId(
}

function isTrustedSc(request: ScCallRequest, state: GlobalState) {
if (request.requireIdentity) {
return false;
}

const trustedScs = state.settings.trustedScs;

const trustedSc = trustedScs.find(
Expand All @@ -206,22 +213,30 @@ function isTrustedSc(request: ScCallRequest, state: GlobalState) {

async function submitScCall(request: ScCallRequest, password: string, dispatch: Dispatch, state: GlobalState) {
if (isTrustedSc(request, state)) {
// fixme: add support for account+identity password
await dispatch(Actions.password.setPassword(password));
}

const response = await timeout(scCall(request, password), 15000);

if (response.Result.State === 0) {
throw new Error('OTHER');
if (typeof response === 'string') {
dispatch(Actions.transactionRequests.updateRequest<ScCallRequest>(request.id, { presignedTransaction: response }));
return undefined;
} else {
if (response.Result.State === 0) {
throw new Error('OTHER');
}

const notify = response.Result.Notify.filter((element: any) => element.ContractAddress === request.contract).map(
(element: any) => element.States,
);
return {
result: notify,
transaction: response.Result.TxHash,
};
}

const notify = response.Result.Notify.filter((element: any) => element.ContractAddress === request.contract).map(
(element: any) => element.States,
);
return {
result: notify,
transaction: response.Result.TxHash,
};

}

async function submitMessageSign(request: MessageSignRequest, password: string) {
Expand Down
13 changes: 11 additions & 2 deletions src/background/requestsManager.ts
@@ -1,4 +1,6 @@
import { Parameter } from 'ontology-dapi';
import { getWallet } from 'src/api/authApi';
import { hasIdentity } from 'src/api/identityApi';
import { v4 as uuid } from 'uuid';
import { Deferred } from '../deffered';
import Actions from '../redux/actions';
Expand Down Expand Up @@ -109,6 +111,13 @@ export class RequestsManager {
gasLimit?: number;
requireIdentity?: boolean;
}) {
const state = this.store.getState();
const wallet = getWallet(state.wallet.wallet!);

if (args.requireIdentity && !hasIdentity(wallet)) {
return Promise.reject('NO_IDENTITY');
}

const requestId = uuid();

// stores deferred object to resolve when the transaction is resolved
Expand All @@ -123,12 +132,12 @@ export class RequestsManager {
}),
);

const state = this.store.getState();
const password = state.password.password;
const trustedScs = state.settings.trustedScs;

if (password !== undefined) {
if (password !== undefined && args.requireIdentity !== true) {
// check if we already have password stored
// whitelisting is not supported for account+identity sign

const trustedSc = trustedScs.find(
(t) =>
Expand Down
4 changes: 4 additions & 0 deletions src/popup/backgroundManager.ts
Expand Up @@ -42,6 +42,10 @@ class BackgroundManager {
return this.rpc.call<boolean>('check_account_password', password);
}

public checkIdentityPassword(password: string) {
return this.rpc.call<boolean>('check_identity_password', password);
}

public checkOntId(encodedIdentity: string, password: string) {
return this.rpc.call<boolean>('check_ont_id', encodedIdentity, password);
}
Expand Down
6 changes: 5 additions & 1 deletion src/popup/pages/call/call.tsx
Expand Up @@ -49,6 +49,7 @@ const enhancer = (Component: React.ComponentType<Props>) => (props: RouteCompone
reduxConnect(mapStateToProps, mapDispatchToProps, (reduxProps, actions, getReduxProps) =>
withProps(
{
allowWhitelist: !get(reduxProps.requests.find((r) => r.id === get(props.location, 'state.requestId'))!, 'requireIdentity', false),
handleCancel: async () => {
props.history.goBack();

Expand Down Expand Up @@ -78,8 +79,11 @@ const enhancer = (Component: React.ComponentType<Props>) => (props: RouteCompone
method,
} as Partial<ScCallRequest>);

if (reduxProps.password !== undefined) {
const requireIdentity = get(reduxProps.requests.find((r) => r.id === get(props.location, 'state.requestId'))!, 'requireIdentity', false)

if (reduxProps.password !== undefined && requireIdentity !== true) {
// check if we already have password stored
// whitelisting is not supported for account+identity sign

const trustedSc = reduxProps.trustedScs.find(
(t) =>
Expand Down

0 comments on commit 65676d8

Please sign in to comment.