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

Add API for Random Module - Closes #6779 #6860

Merged
Merged
46 changes: 45 additions & 1 deletion framework/src/modules/random/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,50 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { codec } from '@liskhq/lisk-codec';
import { BlockAssets, ImmutableAPIContext } from '../../node/state_machine';
import { BaseAPI } from '../base_api';
import { EMPTY_KEY } from '../validators/constants';
import { STORE_PREFIX_RANDOM } from './constants';
import { seedRevealSchema, blockHeaderAssetRandomModule } from './schemas';
import { BlockHeaderAssetRandomModule, ValidatorReveals } from './types';
import { getSeedRevealValidity, getRandomSeed } from './utils';

export class RandomAPI extends BaseAPI {}
export class RandomAPI extends BaseAPI {
public async isSeedRevealValid(
apiContext: ImmutableAPIContext,
generatorAddress: Buffer,
blockAssets: BlockAssets,
): Promise<boolean> {
const randomDataStore = apiContext.getStore(this.moduleID, STORE_PREFIX_RANDOM);
const { validatorReveals } = await randomDataStore.getWithSchema<ValidatorReveals>(
EMPTY_KEY,
seedRevealSchema,
);
const asset = blockAssets.getAsset(this.moduleID);
if (!asset) {
throw new Error('Block asset is missing.');
}

const { seedReveal } = codec.decode<BlockHeaderAssetRandomModule>(
blockHeaderAssetRandomModule,
asset,
);

return getSeedRevealValidity(generatorAddress, seedReveal, validatorReveals);
}

public async getRandomBytes(
apiContext: ImmutableAPIContext,
height: number,
numberOfSeeds: number,
): Promise<Buffer> {
const randomDataStore = apiContext.getStore(this.moduleID, STORE_PREFIX_RANDOM);
const { validatorReveals } = await randomDataStore.getWithSchema<ValidatorReveals>(
EMPTY_KEY,
seedRevealSchema,
);

return getRandomSeed(height, numberOfSeeds, validatorReveals);
}
}
2 changes: 2 additions & 0 deletions framework/src/modules/random/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ export const DEFAULT_MAX_LENGTH_REVEALS = 206;
export const STORE_PREFIX_RANDOM = 0x0000;
export const STORE_PREFIX_REGISTERED_HASH_ONION = Buffer.from('00', 'hex');
export const STORE_PREFIX_USED_HASH_ONION = Buffer.from('01', 'hex');
export const EMPTY_KEY = Buffer.alloc(0);
export const SEED_REVEAL_HASH_SIZE = 16;
2 changes: 1 addition & 1 deletion framework/src/modules/random/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export const seedRevealSchema = {
required: ['generatorAddress', 'seedReveal', 'height', 'valid'],
},
},
required: ['validatorReveals'],
},
required: ['validatorReveals'],
};

export const blockHeaderAssetRandomModule = {
Expand Down
15 changes: 15 additions & 0 deletions framework/src/modules/random/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,18 @@ export interface GeneratorConfig {
readonly defaultPassword?: string;
readonly waitThreshold: number;
}

export interface ValidatorSeedReveal {
generatorAddress: Buffer;
seedReveal: Buffer;
height: number;
valid: boolean;
}

export interface ValidatorReveals {
validatorReveals: ValidatorSeedReveal[];
}

export interface BlockHeaderAssetRandomModule {
seedReveal: Buffer;
}
75 changes: 75 additions & 0 deletions framework/src/modules/random/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,78 @@
*
* Removal or modification of this copyright notice is prohibited.
*/

import * as cryptography from '@liskhq/lisk-cryptography';
import { intToBuffer } from '@liskhq/lisk-cryptography';
import { SEED_REVEAL_HASH_SIZE } from './constants';
import { ValidatorSeedReveal } from './types';

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;
}
}

if (
!lastSeed ||
lastSeed.seedReveal.equals(cryptography.hash(seedReveal).slice(0, SEED_REVEAL_HASH_SIZE))
) {
return true;
}

return false;
};

export const getRandomSeed = (
height: number,
numberOfSeeds: number,
validatorsReveal: ValidatorSeedReveal[],
) => {
if (height < 0 || numberOfSeeds < 0) {
throw new Error('Height or number of seeds cannot be negative.');
}
const initRandomBuffer = intToBuffer(height + numberOfSeeds, 4);
let randomSeed = cryptography.hash(initRandomBuffer).slice(0, 16);
const currentSeeds = validatorsReveal.filter(
v => height <= v.height && v.height <= height + numberOfSeeds,
);
for (const seedObject of currentSeeds) {
if (seedObject.valid) {
randomSeed = bitwiseXOR([randomSeed, seedObject.seedReveal]);
}
}

return randomSeed;
};

export const bitwiseXOR = (bufferArray: Buffer[]): Buffer => {
if (bufferArray.length === 1) {
return bufferArray[0];
}

const bufferSizes = new Set(bufferArray.map(buffer => buffer.length));
if (bufferSizes.size > 1) {
throw new Error('All input for XOR should be same size');
}
const outputSize = [...bufferSizes][0];
Comment on lines +75 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about this? (maybe with better naming)

const isBufferLengthSame = bufferArray.every((elem, _, arr) => elem.length === arr[0].length);
if (!isBufferLengthSame) {
    throw new Error('All input for XOR should be same size');
}
const outputSize = bufferArray[0].length;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can improve this later, I copied this function from other place, it better to resolve it in a separate issue. Also, we can move to common utils if we find more usage of this func

const result = Buffer.alloc(outputSize, 0);

for (let i = 0; i < outputSize; i += 1) {
// eslint-disable-next-line no-bitwise
result[i] = bufferArray.map(b => b[i]).reduce((a, b) => a ^ b, 0);
}

return result;
};
Loading