Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/common/mpc/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ export class MultiPartyComputation {
}

private async process(network: IMpcNetwork, timeout: number) {
let execData = {
networkId: network!.id,
init: Date.now()
};
this.log = logger(`muon:common:mpc:${this.ConstructorName}`);
try {
/** Some partners may be excluded during the MPC process. */
Expand All @@ -210,6 +214,9 @@ export class MultiPartyComputation {
for (let r = 0; r < this.rounds.length; r++) {
Object.freeze(qualifiedPartners);
const roundStartTime = Date.now();
execData["round " + r] = {
computation: {start: Date.now()}
};

const currentRound = this.rounds[r], previousRound = r>0 ? this.rounds[r-1] : null;
this.log(`processing round mpc[${this.id}].${currentRound} ...`)
Expand All @@ -230,6 +237,7 @@ export class MultiPartyComputation {
this.roundsOutput[currentRound] = await this.processRound(r, inputs, broadcasts, network.id, qualifiedPartners);
this.log(`round executed [${network.id}].mpc[${this.id}].${currentRound}`)
this.roundsPromise.resolve(r, true);
execData["round " + r]["computation"]["end"] = Date.now();

/** Gather other partners data */
const dataToSend = {
Expand All @@ -238,11 +246,18 @@ export class MultiPartyComputation {
this.log(`mpc[${this.id}].${currentRound} collecting round data`)
let allPartiesResult: (PartnerRoundReceive|null)[] = await Promise.all(
qualifiedPartners.map(partner => {
const start = Date.now();
return this.tryToGetRoundData(network, partner, r, dataToSend)
.catch(e => {
this.log.error(`[${this.id}][${currentRound}] error at node[${partner}] round ${r} %o`, e)
return null
})
.finally(()=>{
execData["round " + r]["request node " + partner] = {
start: start,
end: Date.now()
}
})
})
)
this.log(`MPC[${this.id}].${currentRound} ${allPartiesResult.filter(i => !!i).length} nodes response received`)
Expand All @@ -266,8 +281,9 @@ export class MultiPartyComputation {
}
}


this.log(`MPC[${this.id}] all rounds done.`)
const result = this.onComplete(this.roundsArrivedMessages, network.id, qualifiedPartners);
const result = this.onComplete(this.roundsArrivedMessages, network.id, qualifiedPartners, execData);
this.roundsPromise.resolve(this.rounds.length, result);
}
catch (e) {
Expand All @@ -276,7 +292,9 @@ export class MultiPartyComputation {
}
}

onComplete(roundsArrivedMessages: MapOf<MapOf<{send: any, broadcast: any}>>, networkId: string, partners: string[]): any { return "" }
onComplete(roundsArrivedMessages: MapOf<MapOf<{ send: any, broadcast: any }>>, networkId: string, partners: string[], execData: any): any {
return ""
}

async processRound(roundIndex: number, input: MapOf<any>, broadcast: MapOf<any>, networkId: string, partners: string[]):
Promise<RoundOutput<any, any>> {
Expand Down
15 changes: 10 additions & 5 deletions src/common/mpc/dist-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export type DistKeyJson = {
polynomial?: {
t: number,
Fx: string[]
}
},
execData?: object
}

export class DistKey {
Expand All @@ -28,14 +29,16 @@ export class DistKey {
t: number,
Fx: PublicKey[]
};
execData:object;

constructor(index: string, share: BN, address: string, publicKey : PublicKey, partners: string[], polynomial?: {t: number, Fx: PublicKey[]}) {
constructor(index: string, share: BN, address: string, publicKey: PublicKey, partners: string[], polynomial?: { t: number, Fx: PublicKey[] }, execData?: any) {
this.index = index;
this.share = share;
this.address = address;
this.publicKey = publicKey;
this.partners = partners,
this.polynomial = polynomial;
this.partners = partners;
this.polynomial = polynomial;
this.execData = execData;
}

/**
Expand Down Expand Up @@ -64,7 +67,8 @@ export class DistKey {
polynomial: !this.polynomial ? undefined : {
t: this.polynomial.t,
Fx: this.polynomial.Fx.map(p => p.encode('hex', true))
}
},
execData: this.execData
}
}

Expand All @@ -83,6 +87,7 @@ export class DistKey {
t: key.polynomial.t,
Fx: key.polynomial.Fx.map(p => TssModule.keyFromPublic(p))
},
key.execData
);
}
}
5 changes: 3 additions & 2 deletions src/common/mpc/dkg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export class DistributedKeyGeneration extends MultiPartyComputation {
return {store, send, broadcast, qualifieds: newQualified}
}

onComplete(roundsArrivedMessages: MapOf<MapOf<{send: any, broadcast: any}>>, networkId: string, qualified: string[]): any {
onComplete(roundsArrivedMessages: MapOf<MapOf<{ send: any, broadcast: any }>>, networkId: string, qualified: string[], execData: any): any {
// console.log(`mpc complete`, roundsArrivedMessages)
const r1Msgs = this.getRoundReceives('round1')
const r2Msgs = this.getRoundReceives('round2')
Expand Down Expand Up @@ -301,7 +301,8 @@ export class DistributedKeyGeneration extends MultiPartyComputation {
{
t: this.t,
Fx: totalFx
}
},
execData
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/plugins/base/app-request-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default class AppRequestManager{
return !!item ? item.request : undefined
}

addSignature(reqId: string, owner: string, sign: string){
addSignature(reqId: string, owner: string, sign){
let item: CacheItem | undefined = this.getItem(reqId);
if(item && item.signatures[owner] === undefined){
item.signatures[owner] = sign
Expand Down
32 changes: 26 additions & 6 deletions src/core/plugins/base/base-app-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ const {utils: {toBN}} = Web3
const ajv = new Ajv()
const clone = (obj) => JSON.parse(JSON.stringify(obj))
const requestConfirmationCache: RedisCache = new RedisCache('req-confirm')

export type AppRequestSignature = {
/**
* Schnorr signature of request, signed by TSS share
*/
signature: string,
execData: object
}
const RemoteMethods = {
WantSign: 'wantSign',
InformRequestConfirmation: 'InformReqConfirmation',
Expand Down Expand Up @@ -335,7 +341,7 @@ class BaseAppPlugin extends CallablePlugin {

// await newRequest.save()

let sign: string = await this.makeSignature(newRequest, result, resultHash)
let sign: AppRequestSignature = await this.makeSignature(newRequest, result, resultHash)
this.requestManager.addSignature(newRequest.reqId, process.env.SIGN_WALLET_ADDRESS!, sign);
// new Signature(sign).save()

Expand Down Expand Up @@ -395,6 +401,11 @@ class BaseAppPlugin extends CallablePlugin {
})
}

//omit execData
for (let i = 0; i < requestData.signatures.length; i++) {
requestData.signatures[i] = omit(requestData.signatures[i], ['execData'])
}

/** send request data to aggregator nodes */
this.log('sending request to aggregator nodes ...')
NetworkIpc.sendToAggregatorNode("AppRequest", requestData)
Expand All @@ -409,6 +420,9 @@ class BaseAppPlugin extends CallablePlugin {
newRequest.save()
}




return requestData
}
}
Expand Down Expand Up @@ -585,10 +599,12 @@ class BaseAppPlugin extends CallablePlugin {

signers = await this.requestManager.onRequestSignFullFilled(newRequest.reqId)


let owners = Object.keys(signers)
let allSignatures = owners.map(w => signers[w]);
let execData = owners.map(w => signers[w].execData);

let schnorrSigns = allSignatures.map(signature => splitSignature(signature))
let schnorrSigns = allSignatures.map(signature => splitSignature(signature.signature))

const ownersIndex = owners.map(wallet => this.nodeManager.getNodeInfo(wallet)!.id);
let aggregatedSign = TssModule.schnorrAggregateSigs(party!.t, schnorrSigns, ownersIndex)
Expand All @@ -605,6 +621,7 @@ class BaseAppPlugin extends CallablePlugin {
ownerPubKey: pub2json(verifyingPubKey, true),
// signers: signersIndices,
signature: bn2hex(aggregatedSign.s),
execData: execData
// sign: {
// s: `0x${aggregatedSign.s.toString(16)}`,
// e: `0x${aggregatedSign.e.toString(16)}`
Expand Down Expand Up @@ -713,7 +730,7 @@ class BaseAppPlugin extends CallablePlugin {
}
}

async makeSignature(request: AppRequest, result: any, resultHash): Promise<string> {
async makeSignature(request: AppRequest, result: any, resultHash): Promise<AppRequestSignature> {
let {reqId} = request;
let nonce: AppTssKey = await this.tssPlugin.getSharedKey(`nonce-${reqId}`, 15000)
if(!nonce)
Expand Down Expand Up @@ -741,10 +758,13 @@ class BaseAppPlugin extends CallablePlugin {
throw {message: "process.env.SIGN_WALLET_ADDRESS is not defined"}
}

return stringifySignature(signature);
return {
signature: stringifySignature(signature),
execData: nonce.distKey.execData
};
}

async __onRemoteSignTheRequest(data: {reqId: string, sign: string} | null, error, remoteNode: MuonNodeInfo) {
async __onRemoteSignTheRequest(data: {reqId: string, sign: AppRequestSignature} | null, error, remoteNode: MuonNodeInfo) {
if(error){
this.log.error(`node ${remoteNode.id} unable to sign the request. %O`, error)
let {request: reqId, ...otherParts} = error;
Expand Down
22 changes: 22 additions & 0 deletions src/core/plugins/tss-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import CallablePlugin from './base/callable-plugin.js'
import AppTssKey, {AppTssKeyJson} from "../../utils/tss/app-tss-key.js";
import lodash from 'lodash'
import * as tssModule from '../../utils/tss/index.js'
import * as execDataStorage from '../../utils/tss/exec-data-storage.js'
import Web3 from 'web3'
import {timeout, stackTrace, uuid, pub2json} from '../../utils/helpers.js'
import {remoteApp, remoteMethod} from './base/app-decorators.js'
Expand Down Expand Up @@ -54,6 +55,7 @@ export type KeyGenOptions = {
const RemoteMethods = {
recoverMyKey: 'recoverMyKey',
storeTssKey: 'storeTssKey',
loadExecData: 'loadExecData',
}

@remoteApp
Expand Down Expand Up @@ -811,6 +813,7 @@ class TssPlugin extends CallablePlugin {
let key = new AppTssKey(party, keyGen.extraParams.keyId!, dKey)

await SharedMemory.set(keyGen.extraParams.keyId, {partyInfo, key: key.toJson()}, 30*60*1000)
execDataStorage.set(key.id, key.distKey.execData);
return key;
}

Expand Down Expand Up @@ -936,6 +939,25 @@ class TssPlugin extends CallablePlugin {
throw "Not permitted to create tss key"
}
}
/**
* Node with ID:[1] inform other nodes that tss creation completed.
*
* @param data
* @param callerInfo: caller node information
* @param callerInfo.wallet: collateral wallet of caller node
* @param callerInfo.peerId: PeerID of caller node
* @returns {Promise<boolean>}
* @private
*/
@remoteMethod(RemoteMethods.loadExecData)
async __loadExecData(data, callerInfo) {
if(!callerInfo.isDeployer)
return "Only deployers are allowed to call this method";
let execData = await execDataStorage.get(data.keyId);
if (execData)
execData = JSON.parse(execData);
return execData;
}
}

export default TssPlugin;
9 changes: 6 additions & 3 deletions src/utils/tss/app-tss-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export type AppTssKeyJson = {
polynomial?: {
t: number,
Fx: string[]
}
},
execData?: object
}

/**
Expand All @@ -34,7 +35,7 @@ export default class AppTssKey {
*/
party: Party | null = null;

private distKey: DistKey;
public distKey: DistKey;

private partnersPubKey = {}

Expand Down Expand Up @@ -76,7 +77,8 @@ export default class AppTssKey {
polynomial: !this.distKey.polynomial ? undefined : {
t: this.distKey.polynomial.t,
Fx: this.distKey.polynomial.Fx.map(p => p.encode('hex', true))
}
},
execData: this.distKey.execData
}
}

Expand All @@ -91,6 +93,7 @@ export default class AppTssKey {
address: tssModule.pub2addr(tssModule.keyFromPublic(key.publicKey)),
partners: key.partners,
polynomial: key.polynomial,
execData: key.execData,
})
)
}
Expand Down
27 changes: 27 additions & 0 deletions src/utils/tss/exec-data-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {createClient, RedisClient} from 'redis'
import redisConfig from '../../common/redis-config.js'
import {promisify} from "util"

const redis = createClient(redisConfig);
const redisGet: (...args) => Promise<any> = promisify(redis.get).bind(redis);
const redisSet: (...args) => Promise<any> = promisify(redis.set).bind(redis);

redis.on("error", function (error) {
console.error(`muon.utils.useOneTime error`, error);
});

/**
* Store execution data of a tss generation operation
* Execution data includes time that took to complete computation of generating the key
* and logs of networking times between nodes
*/
export async function set(key, val) {
val = JSON.stringify(val);
const expireMinutes = 30;
redisSet(key, val, 'EX', expireMinutes * 60);
}

export async function get(key) {
let val = await redisGet(key);
return val;
}