Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign and Verify exposed to Bob3 #15

Merged
merged 4 commits into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28,413 changes: 28,402 additions & 11 deletions package-lock.json

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions src/background/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,52 @@ const controllers: {
})
},

[MessageTypes.SIGN_MESSAGE]: async (app, message) => {
const {payload} = message;
const {address, msg} = payload;
return new Promise(async (resolve, reject) => {
const queue = await app.exec('wallet', 'getTxQueue');

if (queue.length) {
return reject(new Error('user has unconfirmed tx.'));
}

app.exec('analytics', 'track', {
name: 'Bob3 Sign',
});

const requestJson = await app.exec('wallet', 'createSignMessageRequest', msg, address);

await app.exec('wallet', 'addTxToQueue', requestJson);

const popup = await openPopup();
closePopupOnAcceptOrReject(app, resolve, reject, popup);
});
},

[MessageTypes.SIGN_MESSAGE_WITH_NAME]: async (app, message) => {
const {payload} = message;
const {name, msg} = payload;
return new Promise(async (resolve, reject) => {
const queue = await app.exec('wallet', 'getTxQueue');

if (queue.length) {
return reject(new Error('user has unconfirmed tx.'));
}

app.exec('analytics', 'track', {
name: 'Bob3 Sign with Name',
});

const requestJson = await app.exec('wallet', 'createSignMessageRequest', msg, undefined, name);

await app.exec('wallet', 'addTxToQueue', requestJson);

const popup = await openPopup();
closePopupOnAcceptOrReject(app, resolve, reject, popup);
});
},

[MessageTypes.SEND_TX]: async (app, message) => {
const {payload} = message;
const {amount, address} = payload;
Expand Down Expand Up @@ -394,6 +440,18 @@ const controllers: {
return app.exec('node', 'getLatestBlock');
},

[MessageTypes.VERIFY_MESSAGE]: async (app, message) => {
const {payload} = message;
const {msg,signature,address} = payload;
return app.exec('node', 'verifyMessage', msg, signature, address);
},

[MessageTypes.VERIFY_MESSAGE_WITH_NAME]: async (app, message) => {
const {payload} = message;
const {msg,signature,name} = payload;
return app.exec('node', 'verifyMessageWithName', msg, signature, name);
},

[MessageTypes.GET_API]: async (app, message) => {
return app.exec('setting', 'getAPI');
},
Expand Down
52 changes: 52 additions & 0 deletions src/background/services/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ const bdb = require('bdb');
const DB = require('bdb/lib/db');
const rules = require("hsd/lib/covenants/rules");
import {get, put} from '@src/util/db';
const {states,statesByVal} = require('hsd/lib/covenants/namestate');
const Network = require("hsd/lib/protocol/network");
const networkType = process.env.NETWORK_TYPE || 'main';

const NAME_CACHE: string[] = [];
const NAME_MAP: { [hash: string]: string } = {};
export default class NodeService extends GenericService {
store: typeof DB;
network: typeof Network;

async getHeaders(): Promise<any> {
const { apiHost, apiKey } = await this.exec('setting', 'getAPI');
Expand Down Expand Up @@ -129,6 +133,53 @@ export default class NodeService extends GenericService {
return name;
}

async verifyMessage(msg: string, signature: string, address: string) {
if(!msg || !signature || !address) {
throw new Error('Required paremeters include msg as a string, signature as a string, and address as a string.');
}

const headers = await this.getHeaders();
const result = await this.fetch(null, {
method: 'POST',
headers: headers,
body: JSON.stringify({
method: 'verifymessage',
params: [address, signature, msg]
}),
});
if(result.error) {
throw new Error('Error when verifymessage');
}
else {
return result.result;
}
}

async verifyMessageWithName(msg: string, signature: string, name: string) {
if(!msg || !signature || !name) {
throw new Error('Required paremeters include msg as a string, signature as a string, and name as a string.');
}
if(!rules.verifyName(name))
throw new Error('Invalid name.');

const ni = await this.getNameInfo(name);
const ownerHash = ni.result.info.owner.hash;
const ownerIndex = ni.result.info.owner.index;
const state = ni.result.info.state;

if(!ownerHash)
throw new Error('Could not find owner');
else if(state!==statesByVal[states.CLOSED])
throw new Error('Invalid name state.');

const address = await this.getCoin(ownerHash, ownerIndex);

if(!address)
throw new Error('Could not find owner');

return await this.verifyMessage(msg, signature, address.address);
}

async getNameInfo(tld: string) {
const headers = await this.getHeaders();
return this.fetch(null, {
Expand Down Expand Up @@ -225,6 +276,7 @@ export default class NodeService extends GenericService {
async start() {
this.store = bdb.create('/node-store');
await this.store.open();
this.network = Network.get(networkType);
}

async stop() {
Expand Down
155 changes: 141 additions & 14 deletions src/background/services/wallet/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {GenericService} from "@src/util/svc";
import crypto from 'crypto';
const Mnemonic = require('hsd/lib/hd/mnemonic');
const WalletDB = require("hsd/lib/wallet/walletdb");
const Network = require("hsd/lib/protocol/network");
Expand All @@ -19,11 +20,18 @@ const bdb = require('bdb');
const DB = require('bdb/lib/db');
const layout = require('hsd/lib/wallet/layout').txdb;
const {Resource} = require('hsd/lib/dns/resource');
const blake2b = require('bcrypto/lib/blake2b');
import {get, put} from '@src/util/db';
import pushMessage from "@src/util/pushMessage";
import {ActionType as WalletActionType, setWalletBalance} from "@src/ui/ducks/wallet";
import {ActionType as AppActionType} from "@src/ui/ducks/app";
import {ActionType, setTransactions, Transaction} from "@src/ui/ducks/transactions";
import {
ActionType,
setTransactions,
SIGN_MESSAGE_METHOD,
SIGN_MESSAGE_WITH_NAME_METHOD, SignMessageRequest,
Transaction
} from "@src/ui/ducks/transactions";
import {ActionTypes, setDomainNames} from "@src/ui/ducks/domains";
import {ActionType as QueueActionType, setTXQueue } from "@src/ui/ducks/queue";
import {toDollaryDoos} from "@src/util/number";
Expand All @@ -36,6 +44,7 @@ const {types, typesByVal} = rules;

const networkType = process.env.NETWORK_TYPE || 'main';
const LOOKAHEAD = 100;
const MAGIC_STRING = `handshake signed message:\n`;

export default class WalletService extends GenericService {
network: typeof Network;
Expand Down Expand Up @@ -1058,7 +1067,7 @@ export default class WalletService extends GenericService {
});
};

submitTx = async (opts: {txJSON: Transaction; password: string}) => {
submitTx = async (opts: {txJSON: Transaction|SignMessageRequest; password: string}) => {
const walletId = this.selectedID;
const wallet = await this.wdb.get(walletId);

Expand All @@ -1071,23 +1080,43 @@ export default class WalletService extends GenericService {
}
});

const latestBlockNow = await this.exec('node', 'getLatestBlock');
this.wdb.height = latestBlockNow.height;
const mtx = MTX.fromJSON(opts.txJSON);
const tx = await wallet.sendMTX(mtx, this.passphrase);
let returnValue;

if (opts.txJSON.method === SIGN_MESSAGE_WITH_NAME_METHOD) {
returnValue = await this.signMessageWithName(opts.txJSON.data.name!, opts.txJSON.data.message);
}

if (opts.txJSON.method === SIGN_MESSAGE_METHOD) {
returnValue = await this.signMessage(opts.txJSON.data.address!, opts.txJSON.data.message);
}

if (!opts.txJSON.method) {
const latestBlockNow = await this.exec('node', 'getLatestBlock');
this.wdb.height = latestBlockNow.height;
const mtx = MTX.fromJSON(opts.txJSON);
const tx = await wallet.sendMTX(mtx, this.passphrase);
await this.exec('node', 'sendRawTransaction', tx.toHex());
returnValue = tx.getJSON(this.network);
}

await this.removeTxFromQueue(opts.txJSON);
await this.exec('node', 'sendRawTransaction', tx.toHex());
const json = tx.getJSON(this.network);
this.emit('txAccepted', json);
return json;
this.emit('txAccepted', returnValue);
return returnValue;
};

async _addOutputPathToTxQueue(queue: Transaction[]): Promise<Transaction[]> {
async _addOutputPathToTxQueue(queue: Transaction[]|SignMessageRequest[]): Promise<Transaction[]|SignMessageRequest[]> {
for (let i = 0; i < queue.length; i++) {
const tx = queue[i];
for (let outputIndex = 0; outputIndex < tx.outputs.length; outputIndex++) {
const output = tx.outputs[outputIndex];
output.owned = await this.hasAddress(output.address);

if (tx.method) {
continue;
}

if (!tx.method) {
for (let outputIndex = 0; outputIndex < tx.outputs.length; outputIndex++) {
const output = tx.outputs[outputIndex];
output.owned = await this.hasAddress(output.address);
}
}
}

Expand Down Expand Up @@ -1220,6 +1249,104 @@ export default class WalletService extends GenericService {
}
};

createSignMessageRequest = async (message: string, address?: string, name?: string): Promise<SignMessageRequest> => {
const walletId = this.selectedID;

if (typeof address === 'string') {
return {
hash: crypto.createHash('sha256').update(Buffer.from(address + message + Date.now()).toString('hex')).digest('hex'),
method: SIGN_MESSAGE_METHOD,
walletId: walletId,
data: {
address,
message,
},
bid: undefined,
height: 0,
};
}

if (typeof name === 'string') {
return {
hash: crypto.createHash('sha256').update(Buffer.from(name + message + Date.now()).toString('hex')).digest('hex'),
method: SIGN_MESSAGE_WITH_NAME_METHOD,
walletId: walletId,
data: {
name,
message,
},
bid: undefined,
height: 0,
}
}

throw new Error('name or address must be present');
};

signMessage = async(address: string, msg: string): Promise<string> => {
if(!address || !msg) {
throw new Error('Requires parameters address of type string and msg of type string.');
}

const walletId = this.selectedID;
const wallet = await this.wdb.get(walletId);

try {
await wallet.unlock(this.passphrase, 60000);
const key = await wallet.getKey(Address.from(address));

if(!key) {
throw new Error('Address not found.');
}

if(!wallet.master.key) {
throw new Error('Wallet is locked');
}

const _msg = Buffer.from(MAGIC_STRING + msg, 'utf8');
const hash = blake2b.digest(_msg);

const sig = key.sign(hash);

return sig.toString('base64');
} finally {
await wallet.lock();
}
};

signMessageWithName = async (name: string, msg: string): Promise<string> => {
if (!name || !msg) {
throw new Error('Requires parameters name of type string and msg of type string.');
} else if (!rules.verifyName(name)) {
throw new Error('Requires valid name per Handshake protocol rules.');
}

const walletId = this.selectedID;
const wallet = await this.wdb.get(walletId);

try {
await wallet.unlock(this.passphrase, 60000);

const ns = await wallet.getNameStateByName(name);

if(!ns || !ns.owner) {
throw new Error('Cannot find the name owner.');
}

const coin = await wallet.getCoin(ns.owner.hash, ns.owner.index);

if(!coin) {
throw new Error('Cannot find the address of the name owner.');
}

const address = coin.address.toString(this.network);

return this.signMessage(address, msg);
} finally {
await wallet.lock();
}
};

async shouldContinue() {
if (this.forceStopRescan) {
this.forceStopRescan = false;
Expand Down