Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Improve seed reveal check #8625

Merged
merged 2 commits into from
Jun 21, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions framework/src/modules/random/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
setHashOnionUsageRequest,
} from './schemas';
import { ValidatorRevealsStore } from './stores/validator_reveals';
import { getSeedRevealValidity } from './utils';
import { isSeedValidInput } from './utils';
import { HashOnionStore } from './stores/hash_onion';
import { UsedHashOnionStoreObject, UsedHashOnionsStore } from './stores/used_hash_onions';

Expand All @@ -48,10 +48,11 @@ export class RandomEndpoint extends BaseEndpoint {
const { validatorReveals } = await randomDataStore.get(ctx, EMPTY_KEY);

return {
valid: getSeedRevealValidity(
valid: isSeedValidInput(
cryptography.address.getAddressFromLisk32Address(generatorAddress),
Buffer.from(seedReveal, 'hex'),
validatorReveals,
false,
),
};
}
Expand Down
4 changes: 2 additions & 2 deletions framework/src/modules/random/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { EMPTY_KEY } from '../validators/constants';
import { blockHeaderAssetRandomModule } from './schemas';
import { ValidatorRevealsStore } from './stores/validator_reveals';
import { BlockHeaderAssetRandomModule } from './types';
import { getSeedRevealValidity, getRandomSeed } from './utils';
import { isSeedValidInput, getRandomSeed } from './utils';

export class RandomMethod extends BaseMethod {
private readonly _moduleName: string;
Expand All @@ -47,7 +47,7 @@ export class RandomMethod extends BaseMethod {
asset,
);

return getSeedRevealValidity(generatorAddress, seedReveal, validatorReveals);
return isSeedValidInput(generatorAddress, seedReveal, validatorReveals, false);
}

public async getRandomBytes(
Expand Down
32 changes: 6 additions & 26 deletions framework/src/modules/random/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,25 @@ import { ValidatorSeedReveal } from './stores/validator_reveals';
export const isSeedValidInput = (
generatorAddress: Buffer,
seedReveal: Buffer,
validatorsReveal: ValidatorSeedReveal[],
validatorReveals: ValidatorSeedReveal[],
previousSeedRequired = true,
) => {
let lastSeed: ValidatorSeedReveal | undefined;
// by construction, validatorsReveal is order by height asc. Therefore, looping from end will give highest value.
for (let i = validatorsReveal.length - 1; i >= 0; i -= 1) {
const validatorReveal = validatorsReveal[i];
for (let i = validatorReveals.length - 1; i >= 0; i -= 1) {
const validatorReveal = validatorReveals[i];
if (validatorReveal.generatorAddress.equals(generatorAddress)) {
lastSeed = validatorReveal;
break;
}
}
// if the last seed is does not exist, seed reveal is invalid for use

if (!lastSeed) {
return false;
return !previousSeedRequired;
}
return lastSeed.seedReveal.equals(utils.hash(seedReveal).slice(0, SEED_LENGTH));
};

export const getSeedRevealValidity = (
generatorAddress: Buffer,
seedReveal: Buffer,
validatorsReveal: ValidatorSeedReveal[],
) => {
let lastSeed: ValidatorSeedReveal | undefined;
let maxheight = 0;
for (const validatorReveal of validatorsReveal) {
if (
validatorReveal.generatorAddress.equals(generatorAddress) &&
validatorReveal.height > maxheight
) {
maxheight = validatorReveal.height;

lastSeed = validatorReveal;
}
}

return !lastSeed || lastSeed.seedReveal.equals(utils.hash(seedReveal).slice(0, SEED_LENGTH));
};

export const getRandomSeed = (
height: number,
numberOfSeeds: number,
Expand Down
64 changes: 63 additions & 1 deletion framework/test/unit/modules/random/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { bitwiseXOR } from '../../../../src/modules/random/utils';
import { utils } from '@liskhq/lisk-cryptography';
import { bitwiseXOR, isSeedValidInput } from '../../../../src/modules/random/utils';
import { bitwiseXORFixtures } from './bitwise_xor_fixtures';
import { ValidatorSeedReveal } from '../../../../src/modules/random/stores/validator_reveals';
import { SEED_LENGTH, ADDRESS_LENGTH } from '../../../../src/modules/random/constants';

describe('Random module utils', () => {
describe('bitwiseXOR', () => {
Expand All @@ -40,4 +43,63 @@ describe('Random module utils', () => {
expect(() => bitwiseXOR(input)).toThrow('All input for XOR should be same size');
});
});

describe('isSeedValidInput', () => {
const generatorAddress = utils.getRandomBytes(ADDRESS_LENGTH);
const seed = utils.getRandomBytes(SEED_LENGTH);
const previousSeed = utils.hash(seed).slice(0, SEED_LENGTH);
let validatorSeedReveals: ValidatorSeedReveal[];

beforeEach(() => {
let height = 100;
validatorSeedReveals = Array(103)
.fill(0)
.map(() => {
height += 1;
return {
generatorAddress: utils.getRandomBytes(ADDRESS_LENGTH),
seedReveal: utils.getRandomBytes(SEED_LENGTH),
height,
valid: true,
};
});
});

it('should return true when a matching seed is provided corresponding to the highest seed from the generator', () => {
validatorSeedReveals[88].generatorAddress = generatorAddress;
validatorSeedReveals[88].seedReveal = previousSeed;

expect(isSeedValidInput(generatorAddress, seed, validatorSeedReveals)).toBe(true);
});

it('should return false when a matching seed is provided, but not corresponding to the highest seed from the generator', () => {
validatorSeedReveals[88].generatorAddress = generatorAddress;
validatorSeedReveals[88].seedReveal = previousSeed;

validatorSeedReveals[99].generatorAddress = generatorAddress;

expect(isSeedValidInput(generatorAddress, seed, validatorSeedReveals)).toBe(false);
});

it('should return false when previous seed exists, but the provided seed does not match', () => {
validatorSeedReveals[88].generatorAddress = generatorAddress;

expect(isSeedValidInput(generatorAddress, seed, validatorSeedReveals)).toBe(false);
});

it('should return false when previous seed is missing and previous seed is required', () => {
expect(isSeedValidInput(generatorAddress, seed, validatorSeedReveals)).toBe(false);
});

it('should return true for any provided seed when previous seed is missing, but it is not required', () => {
expect(
isSeedValidInput(
generatorAddress,
utils.getRandomBytes(SEED_LENGTH),
validatorSeedReveals,
false,
),
).toBe(true);
});
});
});