factom-vote
Version:
JS implementation of the Factom voting specification
187 lines (151 loc) • 9.61 kB
JavaScript
const assert = require('chai').assert,
{ FactomCli } = require('factom'),
sinon = require('sinon'),
nacl = require('tweetnacl'),
{ sign } = nacl,
{ walletdIdentityPublicKeysResolver,
walletdIdentityPrivateKeyResolver,
getVoteIdentity,
getPublicIdentityKey,
getSecretIdentityKey,
isValidIdentityKey,
isValidPublicIdentityKey,
verifyIdentityKeyAssociation,
getSignature,
isValidSecretIdentityKey } = require('../src/factom-identity');
describe('Factom digital identities', function () {
it('Should validate identity key', function () {
assert.isTrue(isValidIdentityKey('idpub2eubg6p18fefnHPW2Z42Wyre8LwqmRbHpkaEfEmJ213cUo8u7w'));
assert.isTrue(isValidIdentityKey('idsec2Vn3VT8FdE1YpcDms8zSvXR4DGzQeMMdeLRP2RbMCSWCFoQDbS'));
assert.isFalse(isValidIdentityKey('idsec2Vn3VT8FdE1YpcDms8zSvXR4DGzQeMMdeLRP2RbMCSWCFoQDbK'));
});
it('Should validate public identity key', function () {
assert.isTrue(isValidPublicIdentityKey('idpub2eubg6p18fefnHPW2Z42Wyre8LwqmRbHpkaEfEmJ213cUo8u7w'));
assert.isTrue(isValidPublicIdentityKey('idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz'));
assert.isFalse(isValidPublicIdentityKey('idsec2Vn3VT8FdE1YpcDms8zSvXR4DGzQeMMdeLRP2RbMCSWCFoQDbS'));
});
it('Should validate secret identity key', function () {
assert.isTrue(isValidSecretIdentityKey('idsec2Vn3VT8FdE1YpcDms8zSvXR4DGzQeMMdeLRP2RbMCSWCFoQDbS'));
assert.isTrue(isValidSecretIdentityKey('idsec1wnZ9FLheMDXZNnnDHXdqZcMiDrgg2hTNzdseNLwFnEot362c4'));
assert.isFalse(isValidSecretIdentityKey('idpub2eubg6p18fefnHPW2Z42Wyre8LwqmRbHpkaEfEmJ213cUo8u7w'));
});
it('Should get public identity key', function () {
assert.equal(getPublicIdentityKey('idsec1wnZ9FLheMDXZNnnDHXdqZcMiDrgg2hTNzdseNLwFnEot362c4'), 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz');
assert.equal(getPublicIdentityKey('idsec2Vn3VT8FdE1YpcDms8zSvXR4DGzQeMMdeLRP2RbMCSWCFoQDbS'), 'idpub2eubg6p18fefnHPW2Z42Wyre8LwqmRbHpkaEfEmJ213cUo8u7w');
assert.equal(getPublicIdentityKey('idpub2eubg6p18fefnHPW2Z42Wyre8LwqmRbHpkaEfEmJ213cUo8u7w'), 'idpub2eubg6p18fefnHPW2Z42Wyre8LwqmRbHpkaEfEmJ213cUo8u7w');
});
it('Should get secret identity key', async function () {
const cli = new FactomCli();
const mock = sinon.mock(cli);
mock.expects('walletdApi')
.once()
.withArgs('identity-key', { public: 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz' })
.returns(Promise.resolve({ secret: 'idsec1wnZ9FLheMDXZNnnDHXdqZcMiDrgg2hTNzdseNLwFnEot362c4' }));
mock.expects('walletdApi')
.once()
.withArgs('identity-key', { public: 'idpub2eubg6p18fefnHPW2Z42Wyre8LwqmRbHpkaEfEmJ213cUo8u7w' })
.returns(Promise.resolve({ secret: 'idsec2Vn3VT8FdE1YpcDms8zSvXR4DGzQeMMdeLRP2RbMCSWCFoQDbS' }));
const privateKeyResolver = walletdIdentityPrivateKeyResolver.bind(null, cli);
assert.equal(await getSecretIdentityKey(privateKeyResolver, 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz'), 'idsec1wnZ9FLheMDXZNnnDHXdqZcMiDrgg2hTNzdseNLwFnEot362c4');
assert.equal(await getSecretIdentityKey(privateKeyResolver, 'idpub2eubg6p18fefnHPW2Z42Wyre8LwqmRbHpkaEfEmJ213cUo8u7w'), 'idsec2Vn3VT8FdE1YpcDms8zSvXR4DGzQeMMdeLRP2RbMCSWCFoQDbS');
assert.equal(await getSecretIdentityKey(privateKeyResolver, 'idsec1wnZ9FLheMDXZNnnDHXdqZcMiDrgg2hTNzdseNLwFnEot362c4'), 'idsec1wnZ9FLheMDXZNnnDHXdqZcMiDrgg2hTNzdseNLwFnEot362c4');
mock.verify();
});
it('Should get vote identity with secret key', async function () {
const CHAIN_ID = '2d98021e3cf71580102224b2fcb4c5c60595e8fdf6fd1b97c6ef63e9fb3ed635',
PUB_KEY = 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz',
SEC_KEY = 'idsec1wnZ9FLheMDXZNnnDHXdqZcMiDrgg2hTNzdseNLwFnEot362c4';
const cli = new FactomCli();
const mock = sinon.mock(cli);
mock.expects('walletdApi')
.once()
.withArgs('identity-key', { public: PUB_KEY })
.returns(Promise.resolve({ secret: SEC_KEY }));
mock.expects('walletdApi')
.once()
.withArgs('active-identity-keys', { chainid: CHAIN_ID, height: undefined })
.returns(Promise.resolve({ keys: [PUB_KEY] }));
const resolvers = {
publicKeysResolver: walletdIdentityPublicKeysResolver.bind(null, cli),
privateKeyResolver: walletdIdentityPrivateKeyResolver.bind(null, cli)
};
const voteIdentity = await getVoteIdentity(resolvers, { chainId: CHAIN_ID, key: PUB_KEY });
mock.verify();
assert.equal(voteIdentity.id, CHAIN_ID);
assert.deepEqual(voteIdentity.publicKey, Buffer.from('c103756200d0c1223c0ee9911196bf06de6ee570b0f45897e2ef39f9abf39d24', 'hex'));
assert.deepEqual(voteIdentity.secretKey, Buffer.from('67fe571d8cbad2c0d0d10b295301eaf631d43ff82f21c7f161448f220ad22c66', 'hex'));
});
it('Should get vote identity with custom signer', async function () {
const CHAIN_ID = '2d98021e3cf71580102224b2fcb4c5c60595e8fdf6fd1b97c6ef63e9fb3ed635',
PUB_KEY = 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz';
const cli = new FactomCli();
const mock = sinon.mock(cli);
mock.expects('walletdApi')
.once()
.withArgs('active-identity-keys', { chainid: CHAIN_ID, height: undefined })
.returns(Promise.resolve({ keys: [PUB_KEY] }));
const resolvers = {
publicKeysResolver: walletdIdentityPublicKeysResolver.bind(null, cli),
};
const voteIdentity = await getVoteIdentity(resolvers, { chainId: CHAIN_ID, key: PUB_KEY, sign() { } });
mock.verify();
assert.equal(voteIdentity.id, CHAIN_ID);
assert.deepEqual(voteIdentity.publicKey, Buffer.from('c103756200d0c1223c0ee9911196bf06de6ee570b0f45897e2ef39f9abf39d24', 'hex'));
assert.isFunction(voteIdentity.sign);
});
it('Should accept identity key association at current height', async function () {
const cli = new FactomCli();
const mock = sinon.mock(cli);
expectIdentityKeysAtHeightCall(mock, '2d98021e3cf71580102224b2fcb4c5c60595e8fdf6fd1b97c6ef63e9fb3ed635', undefined, 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz');
const publicKeysResolver = walletdIdentityPublicKeysResolver.bind(null, cli);
await verifyIdentityKeyAssociation(publicKeysResolver, '2d98021e3cf71580102224b2fcb4c5c60595e8fdf6fd1b97c6ef63e9fb3ed635', 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz');
mock.verify();
});
it('Should accept identity key association at given height', async function () {
const cli = new FactomCli();
const mock = sinon.mock(cli);
mock.expects('getHeights').never();
expectIdentityKeysAtHeightCall(mock, '2d98021e3cf71580102224b2fcb4c5c60595e8fdf6fd1b97c6ef63e9fb3ed635', 7, 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz');
const publicKeysResolver = walletdIdentityPublicKeysResolver.bind(null, cli);
await verifyIdentityKeyAssociation(publicKeysResolver, '2d98021e3cf71580102224b2fcb4c5c60595e8fdf6fd1b97c6ef63e9fb3ed635', 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz', 7);
mock.verify();
});
it('Should reject identity key association', async function () {
const cli = new FactomCli();
const mock = sinon.mock(cli);
expectIdentityKeysAtHeightCall(mock, '2d98021e3cf71580102224b2fcb4c5c60595e8fdf6fd1b97c6ef63e9fb3ed635', 7, 'idpub3bjJcyp2CgzamFdCdpM7JBLuWoBz7kdk7iHKwDXb3i7qaap848');
const publicKeysResolver = walletdIdentityPublicKeysResolver.bind(null, cli);
try {
await verifyIdentityKeyAssociation(publicKeysResolver, '2d98021e3cf71580102224b2fcb4c5c60595e8fdf6fd1b97c6ef63e9fb3ed635', 'idpub3Doj5fqXye8PkX8w83hzPh3PXbiLhrxTZjT6sXmtFQdDyzwymz', 7);
} catch (e) {
assert.instanceOf(e, Error);
mock.verify();
return;
}
throw new Error('Should have throw');
});
it('Should sign using private key', async function () {
const identity = sign.keyPair();
const message = nacl.randomBytes(32);
const sig = await getSignature(identity, message);
assert.isTrue(sign.detached.verify(message, sig, identity.publicKey));
});
it('Should sign using custom signer', async function () {
const publicKey = Buffer.from('a1597a9808b2be2548d7f29c7fcd884a52916aca4bbe442118044e294c2de971', 'hex');
const identity = {
publicKey,
sign: (data) => sign.detached(data, Buffer.from('11de38af16cfc79c0827d7671b501560bfe4161e1801c6db6a4128aa0013d3ffa1597a9808b2be2548d7f29c7fcd884a52916aca4bbe442118044e294c2de971', 'hex'))
};
const sig = await getSignature(identity, Buffer.from('dummy'));
assert.equal(sig.toString('hex'), 'ce88857fbacb8eaa8afffbc90d2ef9ee79d5b79a80d182942e098bf0feac092ca0630a5e2bfe0d4e6e7eee704fe714db6a010c99f4f1f178f3fbeb430fefc40d');
});
function expectIdentityKeysAtHeightCall(mock, chainId, height, key, exactly) {
mock.expects('walletdApi')
.exactly(exactly || 1)
.withArgs('active-identity-keys', {
chainid: chainId,
height: height
})
.returns(Promise.resolve({ keys: [key] }));
}
});