Skip to content
Draft
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
149 changes: 148 additions & 1 deletion src/__tests__/api/master/walletGenerationCallbacks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
createAwmClient,
AdvancedWalletManagerClient,
} from '../../../masterBitgoExpress/clients/advancedWalletManagerClient';
import { createOnchainKeyGenCallback } from '../../../masterBitgoExpress/handlers/walletGenerationCallbacks';
import {
createOnchainKeyGenCallback,
createEddsaMPCv2KeyGenCallbacks,
} from '../../../masterBitgoExpress/handlers/walletGenerationCallbacks';
import { AppMode, KeySource, MasterExpressConfig, TlsMode } from '../../../shared/types';
import { DEFAULT_ASYNC_MODE_CONFIG } from './testUtils';

Expand Down Expand Up @@ -150,4 +153,148 @@ describe('walletGenerationCallbacks', () => {
}).should.be.rejectedWith('Unexpected key source for onchain key generation: bitgo');
});
});

describe('createEddsaMPCv2KeyGenCallbacks', () => {
const enterprise = 'test-enterprise';
const bitgoPublicGpgKey = 'test-bitgo-gpg-key';

beforeEach(() => {
const config = makeConfig({ advancedWalletManagerBackupUrl: backupAwmUrl });
awmUserClient = createAwmClient(config, coin)!;
awmBackupClient = createAwmBackupClient(config, coin)!;
assert(awmUserClient);
assert(awmBackupClient);
});

it('initializeCallback fans out to user and backup AWM in parallel', async () => {
const userInitNock = nock(advancedWalletManagerUrl)
.post(`/api/${coin}/eddsampcv2/keygen/initialize`, {
source: 'user',
enterprise,
bitgoPublicGpgKey,
})
.reply(200, {
gpgPublicKey: 'user-gpg-pub',
signedMsg1: { message: 'user-msg1', signature: 'user-sig1' },
encryptedState: 'user-enc-state',
encryptedStateKey: 'user-enc-state-key',
});

const backupInitNock = nock(backupAwmUrl)
.post(`/api/${coin}/eddsampcv2/keygen/initialize`, {
source: 'backup',
enterprise,
bitgoPublicGpgKey,
})
.reply(200, {
gpgPublicKey: 'backup-gpg-pub',
signedMsg1: { message: 'backup-msg1', signature: 'backup-sig1' },
encryptedState: 'backup-enc-state',
encryptedStateKey: 'backup-enc-state-key',
});

const callbacks = createEddsaMPCv2KeyGenCallbacks(awmUserClient, awmBackupClient);
const result = await callbacks.initializeCallback({ enterprise, bitgoPublicGpgKey });

result.userGpgPublicKey.should.equal('user-gpg-pub');
result.backupGpgPublicKey.should.equal('backup-gpg-pub');
result.userSignedMsg1.message.should.equal('user-msg1');
result.backupSignedMsg1.message.should.equal('backup-msg1');
result.userEncryptedState.should.equal('user-enc-state');
result.backupEncryptedState.should.equal('backup-enc-state');
userInitNock.done();
backupInitNock.done();
});

it('round1Callback fans out to user and backup AWM in parallel', async () => {
const bitgoMsg1 = { message: 'bitgo-msg1', signature: 'bitgo-sig1' };

const userR1Nock = nock(advancedWalletManagerUrl)
.post(`/api/${coin}/eddsampcv2/keygen/round1`, {
source: 'user',
bitgoMsg1,
encryptedState: 'user-enc-state',
encryptedStateKey: 'user-enc-state-key',
})
.reply(200, {
signedMsg2: { message: 'user-msg2', signature: 'user-sig2' },
encryptedState: 'user-enc-state-r1',
encryptedStateKey: 'user-enc-state-key-r1',
});

const backupR1Nock = nock(backupAwmUrl)
.post(`/api/${coin}/eddsampcv2/keygen/round1`, {
source: 'backup',
bitgoMsg1,
encryptedState: 'backup-enc-state',
encryptedStateKey: 'backup-enc-state-key',
})
.reply(200, {
signedMsg2: { message: 'backup-msg2', signature: 'backup-sig2' },
encryptedState: 'backup-enc-state-r1',
encryptedStateKey: 'backup-enc-state-key-r1',
});

const callbacks = createEddsaMPCv2KeyGenCallbacks(awmUserClient, awmBackupClient);
const result = await callbacks.round1Callback({
bitgoMsg1,
userEncryptedState: 'user-enc-state',
userEncryptedStateKey: 'user-enc-state-key',
backupEncryptedState: 'backup-enc-state',
backupEncryptedStateKey: 'backup-enc-state-key',
});

result.userSignedMsg2.message.should.equal('user-msg2');
result.backupSignedMsg2.message.should.equal('backup-msg2');
result.userEncryptedState.should.equal('user-enc-state-r1');
result.backupEncryptedState.should.equal('backup-enc-state-r1');
userR1Nock.done();
backupR1Nock.done();
});

it('finalizeCallback fans out to user and backup AWM in parallel', async () => {
const bitgoMsg2 = { message: 'bitgo-msg2', signature: 'bitgo-sig2' };
const commonPublicKeychain = 'common-keychain-abc123';

const userFinalizeNock = nock(advancedWalletManagerUrl)
.post(`/api/${coin}/eddsampcv2/keygen/finalize`, {
source: 'user',
bitgoMsg2,
commonPublicKeychain,
encryptedState: 'user-enc-state',
encryptedStateKey: 'user-enc-state-key',
})
.reply(200, {
source: 'user',
commonKeychain: commonPublicKeychain,
});

const backupFinalizeNock = nock(backupAwmUrl)
.post(`/api/${coin}/eddsampcv2/keygen/finalize`, {
source: 'backup',
bitgoMsg2,
commonPublicKeychain,
encryptedState: 'backup-enc-state',
encryptedStateKey: 'backup-enc-state-key',
})
.reply(200, {
source: 'backup',
commonKeychain: commonPublicKeychain,
});

const callbacks = createEddsaMPCv2KeyGenCallbacks(awmUserClient, awmBackupClient);
const result = await callbacks.finalizeCallback({
bitgoMsg2,
commonPublicKeychain,
userEncryptedState: 'user-enc-state',
userEncryptedStateKey: 'user-enc-state-key',
backupEncryptedState: 'backup-enc-state',
backupEncryptedStateKey: 'backup-enc-state-key',
});

result.commonKeychain.should.equal(commonPublicKeychain);
userFinalizeNock.done();
backupFinalizeNock.done();
});
});
});
130 changes: 130 additions & 0 deletions src/advancedWalletManager/routers/advancedWalletManagerApiSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,73 @@ const MpcV2RecoveryResponse = {
const MpcV2RecoveryResponseType = t.type(MpcV2RecoveryResponse);
export type MpcV2RecoveryResponseType = t.TypeOf<typeof MpcV2RecoveryResponseType>;

// EdDSA MPCv2 key generation types
const EddsaMPCv2KeyGenSignedMessage = t.type({
message: t.string,
signature: t.string,
});

const EddsaMPCv2KeyGenInitializeRequest = {
source: t.union([t.literal('user'), t.literal('backup')]),
enterprise: t.string,
bitgoPublicGpgKey: t.string,
};
const EddsaMPCv2KeyGenInitializeRequestType = t.type(EddsaMPCv2KeyGenInitializeRequest);
export type EddsaMPCv2KeyGenInitializeRequestType = t.TypeOf<
typeof EddsaMPCv2KeyGenInitializeRequestType
>;

const EddsaMPCv2KeyGenInitializeResponse = {
gpgPublicKey: t.string,
signedMsg1: EddsaMPCv2KeyGenSignedMessage,
encryptedState: t.string,
encryptedStateKey: t.string,
};
const EddsaMPCv2KeyGenInitializeResponseType = t.type(EddsaMPCv2KeyGenInitializeResponse);
export type EddsaMPCv2KeyGenInitializeResponseType = t.TypeOf<
typeof EddsaMPCv2KeyGenInitializeResponseType
>;

const EddsaMPCv2KeyGenRound1Request = {
source: t.union([t.literal('user'), t.literal('backup')]),
bitgoMsg1: EddsaMPCv2KeyGenSignedMessage,
encryptedState: t.string,
encryptedStateKey: t.string,
};
const EddsaMPCv2KeyGenRound1RequestType = t.type(EddsaMPCv2KeyGenRound1Request);
export type EddsaMPCv2KeyGenRound1RequestType = t.TypeOf<typeof EddsaMPCv2KeyGenRound1RequestType>;

const EddsaMPCv2KeyGenRound1Response = {
signedMsg2: EddsaMPCv2KeyGenSignedMessage,
encryptedState: t.string,
encryptedStateKey: t.string,
};
const EddsaMPCv2KeyGenRound1ResponseType = t.type(EddsaMPCv2KeyGenRound1Response);
export type EddsaMPCv2KeyGenRound1ResponseType = t.TypeOf<
typeof EddsaMPCv2KeyGenRound1ResponseType
>;

const EddsaMPCv2KeyGenFinalizeRequest = {
source: t.union([t.literal('user'), t.literal('backup')]),
bitgoMsg2: EddsaMPCv2KeyGenSignedMessage,
commonPublicKeychain: t.string,
encryptedState: t.string,
encryptedStateKey: t.string,
};
const EddsaMPCv2KeyGenFinalizeRequestType = t.type(EddsaMPCv2KeyGenFinalizeRequest);
export type EddsaMPCv2KeyGenFinalizeRequestType = t.TypeOf<
typeof EddsaMPCv2KeyGenFinalizeRequestType
>;

const EddsaMPCv2KeyGenFinalizeResponse = {
source: t.union([t.literal('user'), t.literal('backup')]),
commonKeychain: t.string,
};
const EddsaMPCv2KeyGenFinalizeResponseType = t.type(EddsaMPCv2KeyGenFinalizeResponse);
export type EddsaMPCv2KeyGenFinalizeResponseType = t.TypeOf<
typeof EddsaMPCv2KeyGenFinalizeResponseType
>;

// API Specification
export const AdvancedWalletManagerApiSpec = apiSpec({
'v1.multisig.sign': {
Expand Down Expand Up @@ -481,6 +548,51 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
description: 'Recover a MPC transaction',
}),
},
'v1.eddsampcv2.keygen.initialize': {
post: httpRoute({
method: 'POST',
path: '/api/{coin}/eddsampcv2/keygen/initialize',
request: httpRequest({
params: { coin: t.string },
body: EddsaMPCv2KeyGenInitializeRequest,
}),
response: {
200: EddsaMPCv2KeyGenInitializeResponseType,
...ErrorResponses,
},
description: 'Initialize EdDSA MPCv2 key generation',
}),
},
'v1.eddsampcv2.keygen.round1': {
post: httpRoute({
method: 'POST',
path: '/api/{coin}/eddsampcv2/keygen/round1',
request: httpRequest({
params: { coin: t.string },
body: EddsaMPCv2KeyGenRound1Request,
}),
response: {
200: EddsaMPCv2KeyGenRound1ResponseType,
...ErrorResponses,
},
description: 'EdDSA MPCv2 key generation round 1',
}),
},
'v1.eddsampcv2.keygen.finalize': {
post: httpRoute({
method: 'POST',
path: '/api/{coin}/eddsampcv2/keygen/finalize',
request: httpRequest({
params: { coin: t.string },
body: EddsaMPCv2KeyGenFinalizeRequest,
}),
response: {
200: EddsaMPCv2KeyGenFinalizeResponseType,
...ErrorResponses,
},
description: 'Finalize EdDSA MPCv2 key generation',
}),
},
});

export type AkmApiSpecRouteHandler<
Expand Down Expand Up @@ -613,5 +725,23 @@ export function createKeyGenRouter(
}),
]);

router.post('v1.eddsampcv2.keygen.initialize', [
responseHandler<AdvancedWalletManagerConfig>(async (_req) => {
throw new NotImplementedError('EdDSA MPCv2 key generation initialize not implemented');
}),
]);

router.post('v1.eddsampcv2.keygen.round1', [
responseHandler<AdvancedWalletManagerConfig>(async (_req) => {
throw new NotImplementedError('EdDSA MPCv2 key generation round1 not implemented');
}),
]);

router.post('v1.eddsampcv2.keygen.finalize', [
responseHandler<AdvancedWalletManagerConfig>(async (_req) => {
throw new NotImplementedError('EdDSA MPCv2 key generation finalize not implemented');
}),
]);

return router;
}
Loading
Loading