From 06f0aad04ef64e489db1950c3fe1e5b59525c1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boban=20Milo=C5=A1evi=C4=87?= Date: Sun, 18 Jun 2023 13:19:36 +0200 Subject: [PATCH] Improve seed reveal check --- framework/src/modules/random/endpoint.ts | 5 +- framework/src/modules/random/method.ts | 4 +- framework/src/modules/random/utils.ts | 32 ++-------- .../test/unit/modules/random/utils.spec.ts | 64 ++++++++++++++++++- 4 files changed, 74 insertions(+), 31 deletions(-) diff --git a/framework/src/modules/random/endpoint.ts b/framework/src/modules/random/endpoint.ts index 23a23b4fe2f..fa9f9c1d914 100644 --- a/framework/src/modules/random/endpoint.ts +++ b/framework/src/modules/random/endpoint.ts @@ -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'; @@ -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, ), }; } diff --git a/framework/src/modules/random/method.ts b/framework/src/modules/random/method.ts index 6b5cc7171d8..9e96f0d7945 100644 --- a/framework/src/modules/random/method.ts +++ b/framework/src/modules/random/method.ts @@ -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; @@ -47,7 +47,7 @@ export class RandomMethod extends BaseMethod { asset, ); - return getSeedRevealValidity(generatorAddress, seedReveal, validatorReveals); + return isSeedValidInput(generatorAddress, seedReveal, validatorReveals, false); } public async getRandomBytes( diff --git a/framework/src/modules/random/utils.ts b/framework/src/modules/random/utils.ts index 207eae595c8..293f53cae05 100644 --- a/framework/src/modules/random/utils.ts +++ b/framework/src/modules/random/utils.ts @@ -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, diff --git a/framework/test/unit/modules/random/utils.spec.ts b/framework/test/unit/modules/random/utils.spec.ts index 75a638c128f..f3b3523e984 100644 --- a/framework/test/unit/modules/random/utils.spec.ts +++ b/framework/test/unit/modules/random/utils.spec.ts @@ -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', () => { @@ -34,4 +37,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); + }); + }); });