From bf2611a1be30aa9f0bd9774e194cc0e176f6cbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Mon, 27 Nov 2023 20:09:53 +0900 Subject: [PATCH 01/24] Updated `vite.config.js` so that it matches the `tsconfig.json` --- playground/vite.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/playground/vite.config.js b/playground/vite.config.js index 4025d882..5fcc624e 100644 --- a/playground/vite.config.js +++ b/playground/vite.config.js @@ -10,4 +10,7 @@ export default defineConfig({ allow: [ '..' ] } }, + build: { + target: "es2022" + }, }) From 8eb041ce0323df648adefeb1474fb6cf02ab6156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Tue, 28 Nov 2023 12:12:16 +0900 Subject: [PATCH 02/24] Implemented unbiased variant of `Math:rnd` and `Math:gen_rng_unbiased` --- src/interpreter/lib/std.ts | 28 ++++++++++++++++++++++-- src/interpreter/util.ts | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 40c320e3..3801db0b 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -2,7 +2,7 @@ import { v4 as uuid } from 'uuid'; import seedrandom from 'seedrandom'; import { NUM, STR, FN_NATIVE, FALSE, TRUE, ARR, NULL, BOOL, OBJ, ERROR } from '../value.js'; -import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue } from '../util.js'; +import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue, unbiasedRandomIntegerInRange, cryptoGen64, signedNumber32ToBigUint32 } from '../util.js'; import { AiScriptRuntimeError } from '../../error.js'; import type { Value } from '../value.js'; @@ -412,12 +412,19 @@ export const std: Record = { return NUM(Math.random()); }), + 'Math:rnd_unbiased': FN_NATIVE(([min, max]) => { + assertNumber(min); + assertNumber(max); + const array = new BigUint64Array(1); + const result = unbiasedRandomIntegerInRange(min.value, max.value, array, cryptoGen64); + return typeof result === 'number' ? NUM(result) : NULL; + }), + 'Math:gen_rng': FN_NATIVE(([seed]) => { expectAny(seed); if (seed.type !== 'num' && seed.type !== 'str') return NULL; const rng = seedrandom(seed.value.toString()); - return FN_NATIVE(([min, max]) => { if (min && min.type === 'num' && max && max.type === 'num') { return NUM(Math.floor(rng() * (Math.floor(max.value) - Math.ceil(min.value) + 1) + Math.ceil(min.value))); @@ -425,6 +432,23 @@ export const std: Record = { return NUM(rng()); }); }), + + 'Math:gen_rng_unbiased': FN_NATIVE(([seed]) => { + expectAny(seed); + if (seed.type !== 'num' && seed.type !== 'str') return NULL; + + const rng = seedrandom(seed.value.toString()); + return FN_NATIVE(([min, max]) => { + assertNumber(min); + assertNumber(max); + const result = unbiasedRandomIntegerInRange(min.value, max.value, rng, (rng) => { + const upper = signedNumber32ToBigUint32(rng.int32()); + const lower = signedNumber32ToBigUint32(rng.int32()); + return BigInt.asUintN(64, (upper << 32n) | lower); + }); + return typeof result === 'number' ? NUM(result) : NULL; + }); + }), //#endregion //#region Num diff --git a/src/interpreter/util.ts b/src/interpreter/util.ts index 1d60c0f3..2ede1660 100644 --- a/src/interpreter/util.ts +++ b/src/interpreter/util.ts @@ -193,3 +193,47 @@ export function reprValue(value: Value, literalLike = false, processedObjects = return '?'; } + +function clz64(num: number): number { + const q = num / (2 ** 32); + const r = num % (2 ** 32); + const upper = Math.clz32(q); + const lower = Math.clz32(r) + 32; + return upper < 32 ? upper : lower; +} + +export function cryptoGen64(array: BigUint64Array): bigint | null { + if (array.length < 1) return null; + const generated = crypto.getRandomValues(array)[0]; + if (!generated) { + return null; + } + return generated; +} + +export function signedNumber32ToBigUint32(num: number) : bigint { + return BigInt((num & 0x7fffffff) - (num & 0x80000000)); // bitwise operators always treats numbers as 32bit signed integers, but arithmetic operators don't. +} + +export function unbiasedRandomIntegerInRange(min: number, max: number, generator: G, gen64: (generator: G) => bigint | null): number | null { + const ceilMin = Math.ceil(min); + const floorMax = Math.floor(max); + const signedScale = floorMax - ceilMin; + if (signedScale === 0) return 0; + const scale = Math.abs(signedScale); + const scaleSign = Math.sign(signedScale); + if (!Number.isSafeInteger(scale) || !Number.isSafeInteger(ceilMin) || !Number.isSafeInteger(floorMax)) { + return null; + } + const bigScale = BigInt(scale); + const shift = BigInt(clz64(scale)); // scale is already proven to be a safe integer + let result: bigint; + do { + const generated = gen64(generator); + if (!generated) { + return null; + } + result = generated >> shift; + } while (result > bigScale); + return Number(result) * scaleSign + ceilMin; +} From 5ae8288f47e0b973d7fb41b913a73f2b9fee0eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 17:09:31 +0900 Subject: [PATCH 03/24] Updated `aiscript.api.md` --- etc/aiscript.api.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index c430eb08..0593f82f 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -271,6 +271,9 @@ type Continue_2 = NodeBase_2 & { type: 'continue'; }; +// @public (undocumented) +function cryptoGen64(array: BigUint64Array): bigint | null; + declare namespace Cst { export { isStatement_2 as isStatement, @@ -858,6 +861,9 @@ export class Scope { }; } +// @public (undocumented) +function signedNumber32ToBigUint32(num: number): bigint; + // @public (undocumented) type Statement = Definition | Return | Each | For | Loop | Break | Continue | Assign | AddAssign | SubAssign; @@ -918,6 +924,9 @@ type TypeSource = NamedTypeSource | FnTypeSource; // @public (undocumented) type TypeSource_2 = NamedTypeSource_2 | FnTypeSource_2; +// @public (undocumented) +function unbiasedRandomIntegerInRange(min: number, max: number, generator: G, gen64: (generator: G) => bigint | null): number | null; + // @public (undocumented) const unWrapRet: (v: Value) => Value; @@ -941,7 +950,10 @@ declare namespace utils { valToJs, jsToVal, getLangVersion, - reprValue + reprValue, + cryptoGen64, + signedNumber32ToBigUint32, + unbiasedRandomIntegerInRange } } export { utils } From 94c33c259c8866065f20714e228bf821407b78c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 18:58:41 +0900 Subject: [PATCH 04/24] * Refactored `Math:rnd` to use crypto * Refactored `Math:rnd_unbiased` to use crypto * Refactored `Math:gen_rng_unbiased` to use `SeedRandomWrapper` --- src/interpreter/lib/std.ts | 22 ++++---- src/interpreter/util.ts | 44 ---------------- src/utils/random/CryptoGen.ts | 29 +++++++++++ src/utils/random/randomBase.ts | 92 ++++++++++++++++++++++++++++++++++ src/utils/random/seedrandom.ts | 76 ++++++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 56 deletions(-) create mode 100644 src/utils/random/CryptoGen.ts create mode 100644 src/utils/random/randomBase.ts create mode 100644 src/utils/random/seedrandom.ts diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 3801db0b..b12e0df8 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -2,8 +2,10 @@ import { v4 as uuid } from 'uuid'; import seedrandom from 'seedrandom'; import { NUM, STR, FN_NATIVE, FALSE, TRUE, ARR, NULL, BOOL, OBJ, ERROR } from '../value.js'; -import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue, unbiasedRandomIntegerInRange, cryptoGen64, signedNumber32ToBigUint32 } from '../util.js'; +import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue } from '../util.js'; import { AiScriptRuntimeError } from '../../error.js'; +import { CryptoGen } from '../../utils/random/CryptoGen.js'; +import { SeedRandomWrapper } from '../../utils/random/seedrandom.js'; import type { Value } from '../value.js'; export const std: Record = { @@ -407,17 +409,17 @@ export const std: Record = { 'Math:rnd': FN_NATIVE(([min, max]) => { if (min && min.type === 'num' && max && max.type === 'num') { - return NUM(Math.floor(Math.random() * (Math.floor(max.value) - Math.ceil(min.value) + 1) + Math.ceil(min.value))); + const res = CryptoGen.instance.generateRandomIntegerInRange(min.value, max.value); + return res === null ? NULL : NUM(res); } - return NUM(Math.random()); + return NUM(CryptoGen.instance.generateNumber0To1()); }), 'Math:rnd_unbiased': FN_NATIVE(([min, max]) => { assertNumber(min); assertNumber(max); - const array = new BigUint64Array(1); - const result = unbiasedRandomIntegerInRange(min.value, max.value, array, cryptoGen64); - return typeof result === 'number' ? NUM(result) : NULL; + const res = CryptoGen.instance.generateRandomIntegerInRange(min.value, max.value); + return res === null ? NULL : NUM(res); }), 'Math:gen_rng': FN_NATIVE(([seed]) => { @@ -437,15 +439,11 @@ export const std: Record = { expectAny(seed); if (seed.type !== 'num' && seed.type !== 'str') return NULL; - const rng = seedrandom(seed.value.toString()); + const rng = new SeedRandomWrapper(seed.value); return FN_NATIVE(([min, max]) => { assertNumber(min); assertNumber(max); - const result = unbiasedRandomIntegerInRange(min.value, max.value, rng, (rng) => { - const upper = signedNumber32ToBigUint32(rng.int32()); - const lower = signedNumber32ToBigUint32(rng.int32()); - return BigInt.asUintN(64, (upper << 32n) | lower); - }); + const result = rng.generateRandomIntegerInRange(min.value, max.value); return typeof result === 'number' ? NUM(result) : NULL; }); }), diff --git a/src/interpreter/util.ts b/src/interpreter/util.ts index 2ede1660..1d60c0f3 100644 --- a/src/interpreter/util.ts +++ b/src/interpreter/util.ts @@ -193,47 +193,3 @@ export function reprValue(value: Value, literalLike = false, processedObjects = return '?'; } - -function clz64(num: number): number { - const q = num / (2 ** 32); - const r = num % (2 ** 32); - const upper = Math.clz32(q); - const lower = Math.clz32(r) + 32; - return upper < 32 ? upper : lower; -} - -export function cryptoGen64(array: BigUint64Array): bigint | null { - if (array.length < 1) return null; - const generated = crypto.getRandomValues(array)[0]; - if (!generated) { - return null; - } - return generated; -} - -export function signedNumber32ToBigUint32(num: number) : bigint { - return BigInt((num & 0x7fffffff) - (num & 0x80000000)); // bitwise operators always treats numbers as 32bit signed integers, but arithmetic operators don't. -} - -export function unbiasedRandomIntegerInRange(min: number, max: number, generator: G, gen64: (generator: G) => bigint | null): number | null { - const ceilMin = Math.ceil(min); - const floorMax = Math.floor(max); - const signedScale = floorMax - ceilMin; - if (signedScale === 0) return 0; - const scale = Math.abs(signedScale); - const scaleSign = Math.sign(signedScale); - if (!Number.isSafeInteger(scale) || !Number.isSafeInteger(ceilMin) || !Number.isSafeInteger(floorMax)) { - return null; - } - const bigScale = BigInt(scale); - const shift = BigInt(clz64(scale)); // scale is already proven to be a safe integer - let result: bigint; - do { - const generated = gen64(generator); - if (!generated) { - return null; - } - result = generated >> shift; - } while (result > bigScale); - return Number(result) * scaleSign + ceilMin; -} diff --git a/src/utils/random/CryptoGen.ts b/src/utils/random/CryptoGen.ts new file mode 100644 index 00000000..c167c82f --- /dev/null +++ b/src/utils/random/CryptoGen.ts @@ -0,0 +1,29 @@ +import { RandomBase, readBigUintLittleEndian } from './randomBase.js'; + +export class CryptoGen extends RandomBase { + private static _instance: CryptoGen = new CryptoGen(); + public static get instance() : CryptoGen { + return CryptoGen._instance; + } + + private constructor() { + super(); + } + + protected generateBigUintByBytes(bytes: number): bigint { + let u8a = new Uint8Array(Math.ceil(bytes / 8) * 8); + if (u8a.length < 1 || !Number.isSafeInteger(bytes)) return 0n; + u8a = this.generateBytes(u8a.subarray(0, bytes)); + return readBigUintLittleEndian(u8a.buffer) ?? 0n; + } + + public generateBigUintByBits(bits: number): bigint { + if (bits < 1 || !Number.isSafeInteger(bits)) return 0n; + const bytes = Math.ceil(bits / 8); + const wastedBits = BigInt(bytes * 8 - bits); + return this.generateBigUintByBytes(bytes) >> wastedBits; + } + public generateBytes(array: Uint8Array): Uint8Array { + return crypto.getRandomValues(array); + } +} diff --git a/src/utils/random/randomBase.ts b/src/utils/random/randomBase.ts new file mode 100644 index 00000000..3123fd07 --- /dev/null +++ b/src/utils/random/randomBase.ts @@ -0,0 +1,92 @@ +export const safeIntegerBits = Math.ceil(Math.log2(Number.MAX_SAFE_INTEGER)); +export const bigSafeIntegerBits = BigInt(safeIntegerBits); +export const bigMaxSafeIntegerExclusive = 1n << bigSafeIntegerBits; +export const fractionBits = safeIntegerBits - 1; +export const bigFractionBits = BigInt(fractionBits); + +export abstract class RandomBase { + protected abstract generateBigUintByBytes(bytes: number): bigint; + public abstract generateBigUintByBits(bits: number): bigint; + public abstract generateBytes(array: Uint8Array): Uint8Array; + + public generateNumber0To1(): number { + let res = this.generateBigUintByBits(safeIntegerBits); + let exponent = 1022; + let remainingFractionBits = safeIntegerBits - bitsToRepresent(res); + while (remainingFractionBits > 0 && exponent >= safeIntegerBits) { + exponent -= remainingFractionBits; + res <<= BigInt(remainingFractionBits); + res |= this.generateBigUintByBits(remainingFractionBits); + remainingFractionBits = safeIntegerBits - bitsToRepresent(res); + } + if (remainingFractionBits > 0) { + const shift = Math.min(exponent - 1, remainingFractionBits); + res <<= BigInt(shift); + res |= this.generateBigUintByBits(shift); + exponent = Math.max(exponent - shift, 0); + } + return (Number(res) * 0.5 ** safeIntegerBits) * (0.5 ** (1022 - exponent)); + } + + public generateUniform(maxInclusive: bigint): bigint { + if (maxInclusive < 1) return 0n; + const log2 = maxInclusive.toString(2).length; + const bytes = Math.ceil(log2 / 8); + const wastedBits = BigInt(bytes * 8 - log2); + let result: bigint; + do { + result = this.generateBigUintByBytes(bytes) >> wastedBits; + } while (result > maxInclusive); + return result; + } + + public generateRandomIntegerInRange(min: number, max: number): number | null { + const ceilMin = Math.ceil(min); + const floorMax = Math.floor(max); + const signedScale = floorMax - ceilMin; + if (signedScale === 0) return ceilMin; + const scale = Math.abs(signedScale); + const scaleSign = Math.sign(signedScale); + if (!Number.isSafeInteger(scale) || !Number.isSafeInteger(ceilMin) || !Number.isSafeInteger(floorMax)) { + return null; + } + const bigScale = BigInt(scale); + return Number(this.generateUniform(bigScale)) * scaleSign + ceilMin; + } +} + +export function bitsToRepresent(num: bigint): number { + if (num === 0n) return 0; + return num.toString(2).length; +} + +function readSmallBigUintLittleEndian(buffer: ArrayBufferLike): bigint | null { + if (buffer.byteLength === 0) return null; + if (buffer.byteLength < 8) { + const array = new Uint8Array(8); + array.set(new Uint8Array(buffer)); + return new DataView(array.buffer).getBigUint64(0, true); + } + return new DataView(buffer).getBigUint64(0, true); +} + +export function readBigUintLittleEndian(buffer: ArrayBufferLike): bigint | null { + if (buffer.byteLength === 0) return null; + if (buffer.byteLength <= 8) { + return readSmallBigUintLittleEndian(buffer); + } + const dataView = new DataView(buffer); + let pos = 0n; + let res = 0n; + let index = 0; + for (; index < dataView.byteLength - 7; index += 8, pos += 64n) { + const element = dataView.getBigUint64(index, true); + res |= element << pos; + } + if (index < dataView.byteLength) { + const array = new Uint8Array(8); + array.set(new Uint8Array(buffer, index)); + res |= new DataView(array.buffer).getBigUint64(0, true) << pos; + } + return res; +} diff --git a/src/utils/random/seedrandom.ts b/src/utils/random/seedrandom.ts new file mode 100644 index 00000000..5c6b3d5f --- /dev/null +++ b/src/utils/random/seedrandom.ts @@ -0,0 +1,76 @@ +import seedrandom from 'seedrandom'; +import { RandomBase, readBigUintLittleEndian } from './randomBase.js'; + +const seedRandomBlockSize = Int32Array.BYTES_PER_ELEMENT; + +export class SeedRandomWrapper extends RandomBase { + private rng: seedrandom.PRNG; + private buffer: Uint8Array; + private filledBuffer: Uint8Array; + constructor(seed: string | number) { + super(); + this.rng = seedrandom(seed.toString()); + this.buffer = new Uint8Array(seedRandomBlockSize); + this.filledBuffer = new Uint8Array(0); + } + private fillBuffer(): void { + this.buffer.fill(0); + this.buffer = this.fillBufferDirect(this.buffer); + this.filledBuffer = this.buffer; + } + private fillBufferDirect(buffer: Uint8Array): Uint8Array { + if ((buffer.length % seedRandomBlockSize) !== 0) throw new Error(`SeedRandomWrapper.fillBufferDirect should always be called with the buffer with the length a multiple-of-${seedRandomBlockSize}!`); + const length = buffer.length / seedRandomBlockSize; + const dataView = new DataView(buffer.buffer); + let byteOffset = 0; + for (let index = 0; index < length; index++, byteOffset += seedRandomBlockSize) { + dataView.setInt32(byteOffset, this.rng.int32(), false); + } + return buffer; + } + protected generateBigUintByBytes(bytes: number): bigint { + let u8a = new Uint8Array(Math.ceil(bytes / 8) * 8); + if (u8a.length < 1 || !Number.isSafeInteger(bytes)) return 0n; + u8a = this.generateBytes(u8a.subarray(0, bytes)); + return readBigUintLittleEndian(u8a.buffer) ?? 0n; + } + + public generateBigUintByBits(bits: number): bigint { + if (bits < 1 || !Number.isSafeInteger(bits)) return 0n; + const bytes = Math.ceil(bits / 8); + const wastedBits = BigInt(bytes * 8 - bits); + return this.generateBigUintByBytes(bytes) >> wastedBits; + } + + public generateBytes(array: Uint8Array): Uint8Array { + if (array.length < 1) return array; + array.fill(0); + let dst = array; + if (dst.length <= this.filledBuffer.length) { + dst.set(this.filledBuffer.subarray(0, dst.length)); + this.filledBuffer = this.filledBuffer.subarray(dst.length); + return array; + } else { + while (dst.length > 0) { + if (this.filledBuffer.length === 0) { + if (dst.length >= seedRandomBlockSize) { + const df64 = dst.subarray(0, dst.length - (dst.length % seedRandomBlockSize)); + this.fillBufferDirect(df64); + dst = dst.subarray(df64.length); + continue; + } + this.fillBuffer(); + } + if (dst.length <= this.filledBuffer.length) { + dst.set(this.filledBuffer.subarray(0, dst.length)); + this.filledBuffer = this.filledBuffer.subarray(dst.length); + return array; + } + dst.set(this.filledBuffer); + dst = dst.subarray(this.filledBuffer.length); + this.fillBuffer(); + } + return array; + } + } +} From eb7edd3d1969a0ac9f533fbc971f836d8a7e1095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 19:15:31 +0900 Subject: [PATCH 05/24] Updated `aiscript.api.md` --- etc/aiscript.api.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index 0593f82f..c430eb08 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -271,9 +271,6 @@ type Continue_2 = NodeBase_2 & { type: 'continue'; }; -// @public (undocumented) -function cryptoGen64(array: BigUint64Array): bigint | null; - declare namespace Cst { export { isStatement_2 as isStatement, @@ -861,9 +858,6 @@ export class Scope { }; } -// @public (undocumented) -function signedNumber32ToBigUint32(num: number): bigint; - // @public (undocumented) type Statement = Definition | Return | Each | For | Loop | Break | Continue | Assign | AddAssign | SubAssign; @@ -924,9 +918,6 @@ type TypeSource = NamedTypeSource | FnTypeSource; // @public (undocumented) type TypeSource_2 = NamedTypeSource_2 | FnTypeSource_2; -// @public (undocumented) -function unbiasedRandomIntegerInRange(min: number, max: number, generator: G, gen64: (generator: G) => bigint | null): number | null; - // @public (undocumented) const unWrapRet: (v: Value) => Value; @@ -950,10 +941,7 @@ declare namespace utils { valToJs, jsToVal, getLangVersion, - reprValue, - cryptoGen64, - signedNumber32ToBigUint32, - unbiasedRandomIntegerInRange + reprValue } } export { utils } From 26f0c3a4bfd5523970f6cbcb446ee48a8fd1928b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 19:24:45 +0900 Subject: [PATCH 06/24] * Removed `Math:rnd_unbiased` --- src/interpreter/lib/std.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 5dc7a5f3..ec6ccb2b 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -416,13 +416,6 @@ export const std: Record = { return NUM(CryptoGen.instance.generateNumber0To1()); }), - 'Math:rnd_unbiased': FN_NATIVE(([min, max]) => { - assertNumber(min); - assertNumber(max); - const res = CryptoGen.instance.generateRandomIntegerInRange(min.value, max.value); - return res === null ? NULL : NUM(res); - }), - 'Math:gen_rng': FN_NATIVE(([seed]) => { expectAny(seed); if (seed.type !== 'num' && seed.type !== 'str') return NULL; From 717629308a9b5326b60c18bd9f59d1fb55f69b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 19:57:28 +0900 Subject: [PATCH 07/24] * Updated `CHANGELOG.md` * Updated `std-math.md` --- CHANGELOG.md | 2 ++ docs/std-math.md | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab62e6f9..c7b95a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Fix: チェイン系(インデックスアクセス`[]`、プロパティアクセス`.`、関数呼び出し`()`)と括弧を組み合わせた時に不正な挙動をするバグを修正 - 関数`Str#charcode_at` `Str#to_arr` `Str#to_char_arr` `Str#to_charcode_arr` `Str#to_utf8_byte_arr` `Str#to_unicode_codepoint_arr` `Str:from_unicode_codepoints` `Str:from_utf8_bytes`を追加 - Fix: `Str#codepoint_at`がサロゲートペアに対応していないのを修正 +- Fix: `Math:rnd`が範囲外の値を返す可能性があるのを修正 +- 関数`Math:gen_rng_unbiased`を追加 ## Note バージョン0.16.0に記録漏れがありました。 >- 関数`Str:from_codepoint` `Str#codepoint_at`を追加 diff --git a/docs/std-math.md b/docs/std-math.md index a25b7bec..32ce40eb 100644 --- a/docs/std-math.md +++ b/docs/std-math.md @@ -122,6 +122,11 @@ _min_ および _max_ を渡した場合、_min_ <= x, x <= _max_ の整数、 ### @Math:gen_rng(_seed_: num | str): fn シードから乱数生成機を生成します。 +生成された乱数生成器は 0 <= x, x < 1 の 小数を返します。 + +### @Math:gen_rng_unbiased(_seed_: num | str): @(_min_: num, _max_: num) +シードから _min_ <= x, x <= _max_ の整数を一様分布で生成する乱数生成機を生成します。 +_min_ および _max_ が渡されていない場合はエラーとなります。 ## その他 ### @Math:clz32(_x_: num): num From 27463e647effbd888d735be27ade60d88ac2b397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 20:04:46 +0900 Subject: [PATCH 08/24] Implemented `Math:gen_rng_chacha20` --- package.json | 2 + src/interpreter/lib/std.ts | 14 +++++ src/utils/random/chacha20.ts | 107 +++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 src/utils/random/chacha20.ts diff --git a/package.json b/package.json index 8f4965ed..a387d37b 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,8 @@ "typescript": "5.3.2" }, "dependencies": { + "@types/libsodium-wrappers-sumo": "0.7.8", + "libsodium-wrappers-sumo": "0.7.13", "seedrandom": "3.0.5", "stringz": "2.1.0", "uuid": "9.0.1" diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index ec6ccb2b..bb646214 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -7,6 +7,7 @@ import { AiScriptRuntimeError } from '../../error.js'; import { textDecoder } from '../../const.js'; import { CryptoGen } from '../../utils/random/CryptoGen.js'; import { SeedRandomWrapper } from '../../utils/random/seedrandom.js'; +import { ChaCha20 } from '../../utils/random/chacha20.js'; import type { Value } from '../value.js'; export const std: Record = { @@ -441,6 +442,19 @@ export const std: Record = { return typeof result === 'number' ? NUM(result) : NULL; }); }), + + 'Math:gen_rng_chacha20': FN_NATIVE(async ([seed]) => { + if (seed && seed.type !== 'num' && seed.type !== 'str') return NULL; + await ChaCha20.ready; + const rng = new ChaCha20(typeof seed === 'undefined' ? undefined : seed.value); + return FN_NATIVE(([min, max]) => { + if (min && min.type === 'num' && max && max.type === 'num') { + const result = rng.generateRandomIntegerInRange(min.value, max.value); + return typeof result === 'number' ? NUM(result) : NULL; + } + return NUM(rng.generateNumber0To1()); + }); + }), //#endregion //#region Num diff --git a/src/utils/random/chacha20.ts b/src/utils/random/chacha20.ts new file mode 100644 index 00000000..733c71df --- /dev/null +++ b/src/utils/random/chacha20.ts @@ -0,0 +1,107 @@ +import sodium from 'libsodium-wrappers-sumo'; +import { RandomBase, bigMaxSafeIntegerExclusive, bigSafeIntegerBits, readBigUintLittleEndian, safeIntegerBits } from './randomBase.js'; + +const chacha20BlockSize = 64; +const bigChacha20BlockSize = BigInt(chacha20BlockSize); + +export class ChaCha20 extends RandomBase { + public static ready = sodium.ready; + private key: Uint8Array; + private nonce: Uint8Array; + private buffer: Uint8Array; + private filledBuffer: Uint8Array; + private counter: bigint; + constructor(seed?: string | number | Uint8Array | undefined) { + const keyNonceBytes = sodium.crypto_stream_chacha20_NONCEBYTES + sodium.crypto_stream_chacha20_KEYBYTES; + super(); + let keynonce: Uint8Array; + if (typeof seed === 'undefined') { + keynonce = crypto.getRandomValues(new Uint8Array(keyNonceBytes)); + } else if (typeof seed === 'number') { + const array = new Float64Array([seed]); + keynonce = sodium.crypto_generichash(keyNonceBytes, new Uint8Array(array.buffer)); + } else { + keynonce = sodium.crypto_generichash(keyNonceBytes, seed); + } + this.key = keynonce.subarray(0, sodium.crypto_stream_chacha20_KEYBYTES); + this.nonce = keynonce.subarray(sodium.crypto_stream_chacha20_KEYBYTES); + this.buffer = new Uint8Array(chacha20BlockSize); + this.counter = 0n; + this.filledBuffer = new Uint8Array(0); + } + private fillBuffer(): void { + this.buffer.fill(0); + this.buffer = this.fillBufferDirect(this.buffer); + this.filledBuffer = this.buffer; + } + private fillBufferDirect(buffer: Uint8Array): Uint8Array { + if ((buffer.length % chacha20BlockSize) !== 0) throw new Error('ChaCha20.fillBufferDirect should always be called with the buffer with the length a multiple-of-64!'); + buffer.fill(0); + let blocks = BigInt(buffer.length / chacha20BlockSize); + let counter = this.counter % bigMaxSafeIntegerExclusive; + const newCount = counter + blocks; + const overflow = newCount >> bigSafeIntegerBits; + if (overflow === 0n) { + buffer.set(sodium.crypto_stream_chacha20_xor_ic(buffer, this.nonce, Number(counter), this.key)); + this.counter = newCount; + return buffer; + } + let dst = buffer; + while (dst.length > 0) { + const remainingBlocks = bigMaxSafeIntegerExclusive - counter; + const genBlocks = remainingBlocks > blocks ? blocks : remainingBlocks; + blocks -= genBlocks; + const dbuf = dst.subarray(0, Number(genBlocks * bigChacha20BlockSize)); // safe integers wouldn't lose any precision with multiplying by a power-of-two anyway. + dbuf.set(sodium.crypto_stream_chacha20_xor_ic(dbuf, this.nonce, Number(counter), this.key)); + dst = dst.subarray(dbuf.length); + counter = BigInt.asUintN(safeIntegerBits, counter + genBlocks); + this.counter = counter; + } + return buffer; + } + protected generateBigUintByBytes(bytes: number): bigint { + let u8a = new Uint8Array(Math.ceil(bytes / 8) * 8); + if (u8a.length < 1 || !Number.isSafeInteger(bytes)) return 0n; + u8a = this.generateBytes(u8a.subarray(0, bytes)); + return readBigUintLittleEndian(u8a.buffer) ?? 0n; + } + + public generateBigUintByBits(bits: number): bigint { + if (bits < 1 || !Number.isSafeInteger(bits)) return 0n; + const bytes = Math.ceil(bits / 8); + const wastedBits = BigInt(bytes * 8 - bits); + return this.generateBigUintByBytes(bytes) >> wastedBits; + } + + public generateBytes(array: Uint8Array): Uint8Array { + if (array.length < 1) return array; + array.fill(0); + let dst = array; + if (dst.length <= this.filledBuffer.length) { + dst.set(this.filledBuffer.subarray(0, dst.length)); + this.filledBuffer = this.filledBuffer.subarray(dst.length); + return array; + } else { + while (dst.length > 0) { + if (this.filledBuffer.length === 0) { + if (dst.length >= chacha20BlockSize) { + const df64 = dst.subarray(0, dst.length - (dst.length % chacha20BlockSize)); + this.fillBufferDirect(df64); + dst = dst.subarray(df64.length); + continue; + } + this.fillBuffer(); + } + if (dst.length <= this.filledBuffer.length) { + dst.set(this.filledBuffer.subarray(0, dst.length)); + this.filledBuffer = this.filledBuffer.subarray(dst.length); + return array; + } + dst.set(this.filledBuffer); + dst = dst.subarray(this.filledBuffer.length); + this.fillBuffer(); + } + return array; + } + } +} From a0934f534a7577489af4e4b7b228a7c378a25210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 20:06:48 +0900 Subject: [PATCH 09/24] * Refactored `Math:gen_rng_unbiased` to use ChaCha20 * Refactored `Math:rnd` to use `crypto` * Refactored `Math:rnd_unbiased` to use `crypto` --- src/interpreter/lib/std.ts | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index bb646214..866be440 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -417,6 +417,13 @@ export const std: Record = { return NUM(CryptoGen.instance.generateNumber0To1()); }), + 'Math:rnd_unbiased': FN_NATIVE(([min, max]) => { + assertNumber(min); + assertNumber(max); + const res = CryptoGen.instance.generateRandomIntegerInRange(min.value, max.value); + return res === null ? NULL : NUM(res); + }), + 'Math:gen_rng': FN_NATIVE(([seed]) => { expectAny(seed); if (seed.type !== 'num' && seed.type !== 'str') return NULL; @@ -430,20 +437,7 @@ export const std: Record = { }); }), - 'Math:gen_rng_unbiased': FN_NATIVE(([seed]) => { - expectAny(seed); - if (seed.type !== 'num' && seed.type !== 'str') return NULL; - - const rng = new SeedRandomWrapper(seed.value); - return FN_NATIVE(([min, max]) => { - assertNumber(min); - assertNumber(max); - const result = rng.generateRandomIntegerInRange(min.value, max.value); - return typeof result === 'number' ? NUM(result) : NULL; - }); - }), - - 'Math:gen_rng_chacha20': FN_NATIVE(async ([seed]) => { + 'Math:gen_rng_unbiased': FN_NATIVE(async ([seed]) => { if (seed && seed.type !== 'num' && seed.type !== 'str') return NULL; await ChaCha20.ready; const rng = new ChaCha20(typeof seed === 'undefined' ? undefined : seed.value); From a9767956834d69eb3a0df552053f102fecc04ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 20:06:48 +0900 Subject: [PATCH 10/24] + Added tests for `Math:gen_rng_unbiased` * Reduced potential random test failure --- test/index.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/index.ts b/test/index.ts index 91954f98..dd2afb68 100644 --- a/test/index.ts +++ b/test/index.ts @@ -2862,13 +2862,28 @@ describe('std', () => { const res = await exe(` @test(seed) { let random = Math:gen_rng(seed) - return random(0 100) + return random() } let seed1 = \`{Util:uuid()}\` let seed2 = \`{Date:year()}\` let test1 = if (test(seed1) == test(seed1)) {true} else {false} let test2 = if (test(seed1) == test(seed2)) {true} else {false} - <: [test1 test2] + <: [test1, test2] + `) + eq(res, ARR([BOOL(true), BOOL(false)])); + }); + + test.concurrent('gen_rng_unbiased', async () => { + const res = await exe(` + @test(seed) { + let random = Math:gen_rng_unbiased(seed) + return random() + } + let seed1 = \`{Util:uuid()}\` + let seed2 = \`{Date:year()}\` + let test1 = if (test(seed1) == test(seed1)) {true} else {false} + let test2 = if (test(seed1) == test(seed2)) {true} else {false} + <: [test1, test2] `) eq(res, ARR([BOOL(true), BOOL(false)])); }); From 3ef118a573030473bb2887ec0868af6cd509339a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Fri, 9 Feb 2024 20:29:30 +0900 Subject: [PATCH 11/24] * `Math:gen_rng` now has 2nd parameter `algorithm` --- CHANGELOG.md | 7 ++++-- src/interpreter/lib/std.ts | 45 ++++++++++---------------------------- src/utils/random/genrng.ts | 42 +++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 src/utils/random/genrng.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 765efdca..6ec25dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,13 @@ - 文法エラーの表示を改善。理由を詳細に表示するように。 - 複数行のコメントがある時に文法エラーの表示行数がずれる問題を解消しました。 - 実行時エラーの発生位置が表示されるように。 +- 関数`Math:gen_rng`に第二引数`algorithm`をオプション引数として追加。 + - アルゴリズムを`chacha20`、`rc4`、`rc4_legacy`から選べるようになりました。 + - **Breaking Change** `algorithm`を指定しない場合、`chacha20`が選択されます。 - **Breaking Change** パースの都合によりmatch文の構文を変更。パターンの前に`case`キーワードが必要となり、`*`は`default`に変更。 - **Breaking Change** 多くの予約語を追加。これまで変数名等に使えていた名前に影響が出る可能性があります。 - **Breaking Change** 配列及び関数の引数において、空白区切りが使用できなくなりました。`,`または改行が必要です。 +- **Breaking Change** `Math:rnd`が範囲外の値を返す可能性があるのを修正。 # 0.17.0 - `package.json`を修正 @@ -20,8 +24,7 @@ - 関数`Str#charcode_at` `Str#to_arr` `Str#to_char_arr` `Str#to_charcode_arr` `Str#to_utf8_byte_arr` `Str#to_unicode_codepoint_arr` `Str:from_unicode_codepoints` `Str:from_utf8_bytes`を追加 - Fix: `Str#codepoint_at`がサロゲートペアに対応していないのを修正 - 配列の範囲外および非整数のインデックスへの代入でエラーを出すように -- Fix: `Math:rnd`が範囲外の値を返す可能性があるのを修正 -- 関数`Math:gen_rng_unbiased`を追加 + ## Note バージョン0.16.0に記録漏れがありました。 >- 関数`Str:from_codepoint` `Str#codepoint_at`を追加 diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 73e1660f..96a42886 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -1,14 +1,12 @@ /* eslint-disable no-empty-pattern */ import { v4 as uuid } from 'uuid'; -import seedrandom from 'seedrandom'; import { NUM, STR, FN_NATIVE, FALSE, TRUE, ARR, NULL, BOOL, OBJ, ERROR } from '../value.js'; import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue } from '../util.js'; import { AiScriptRuntimeError } from '../../error.js'; import { AISCRIPT_VERSION } from '../../constants.js'; import { textDecoder } from '../../const.js'; import { CryptoGen } from '../../utils/random/CryptoGen.js'; -import { SeedRandomWrapper } from '../../utils/random/seedrandom.js'; -import { ChaCha20 } from '../../utils/random/chacha20.js'; +import { GenerateChaCha20Random, GenerateLegacyRandom, GenerateRC4Random } from '../../utils/random/genrng.js'; import type { Value } from '../value.js'; export const std: Record = { @@ -418,37 +416,18 @@ export const std: Record = { return NUM(CryptoGen.instance.generateNumber0To1()); }), - 'Math:rnd_unbiased': FN_NATIVE(([min, max]) => { - assertNumber(min); - assertNumber(max); - const res = CryptoGen.instance.generateRandomIntegerInRange(min.value, max.value); - return res === null ? NULL : NUM(res); - }), - - 'Math:gen_rng': FN_NATIVE(([seed]) => { + 'Math:gen_rng': FN_NATIVE(async ([seed, algorithm]) => { expectAny(seed); - if (seed.type !== 'num' && seed.type !== 'str') return NULL; - - const rng = seedrandom(seed.value.toString()); - return FN_NATIVE(([min, max]) => { - if (min && min.type === 'num' && max && max.type === 'num') { - return NUM(Math.floor(rng() * (Math.floor(max.value) - Math.ceil(min.value) + 1) + Math.ceil(min.value))); - } - return NUM(rng()); - }); - }), - - 'Math:gen_rng_unbiased': FN_NATIVE(async ([seed]) => { - if (seed && seed.type !== 'num' && seed.type !== 'str') return NULL; - await ChaCha20.ready; - const rng = new ChaCha20(typeof seed === 'undefined' ? undefined : seed.value); - return FN_NATIVE(([min, max]) => { - if (min && min.type === 'num' && max && max.type === 'num') { - const result = rng.generateRandomIntegerInRange(min.value, max.value); - return typeof result === 'number' ? NUM(result) : NULL; - } - return NUM(rng.generateNumber0To1()); - }); + const algo = !algorithm || algorithm.type !== 'str' ? STR('chacha20') : algorithm; + if (seed.type !== 'num' && seed.type !== 'str' && seed.type !== 'null') return NULL; + switch (algo.value) { + case 'rc4_legacy': + return GenerateLegacyRandom(seed); + case 'rc4': + return GenerateRC4Random(seed); + default: + return GenerateChaCha20Random(seed); + } }), //#endregion diff --git a/src/utils/random/genrng.ts b/src/utils/random/genrng.ts new file mode 100644 index 00000000..30bed298 --- /dev/null +++ b/src/utils/random/genrng.ts @@ -0,0 +1,42 @@ +import seedrandom from 'seedrandom'; +import { FN_NATIVE, NULL, NUM } from '../../interpreter/value.js'; +import { assertNumber } from '../../interpreter/util.js'; +import { SeedRandomWrapper } from './seedrandom.js'; +import type { VNativeFn, VNull, Value } from '../../interpreter/value.js'; +import { ChaCha20 } from './chacha20.js'; + +export function GenerateLegacyRandom(seed: Value | undefined) : VNativeFn | VNull { + if (!seed || seed.type !== 'num' && seed.type !== 'str') return NULL; + const rng = seedrandom(seed.value.toString()); + return FN_NATIVE(([min, max]) => { + if (min && min.type === 'num' && max && max.type === 'num') { + return NUM(Math.floor(rng() * (Math.floor(max.value) - Math.ceil(min.value) + 1) + Math.ceil(min.value))); + } + return NUM(rng()); + }); +} + +export function GenerateRC4Random(seed: Value | undefined) : VNativeFn | VNull { + if (!seed || seed.type !== 'num' && seed.type !== 'str') return NULL; + const rng = new SeedRandomWrapper(seed.value); + return FN_NATIVE(([min, max]) => { + if (min && min.type === 'num' && max && max.type === 'num') { + const result = rng.generateRandomIntegerInRange(min.value, max.value); + return typeof result === 'number' ? NUM(result) : NULL; + } + return NUM(rng.generateNumber0To1()); + }); +} + +export async function GenerateChaCha20Random(seed: Value | undefined) : Promise { + if (!seed || seed.type !== 'num' && seed.type !== 'str' && seed.type !== 'null') return NULL; + await ChaCha20.ready; + const rng = new ChaCha20(seed.type === 'null' ? undefined : seed.value); + return FN_NATIVE(([min, max]) => { + if (min && min.type === 'num' && max && max.type === 'num') { + const result = rng.generateRandomIntegerInRange(min.value, max.value); + return typeof result === 'number' ? NUM(result) : NULL; + } + return NUM(rng.generateNumber0To1()); + }); +} From cd0f8d46163865eb3bb9eade5d33eb4435e515bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Fri, 9 Feb 2024 20:33:16 +0900 Subject: [PATCH 12/24] *Updated `CHANGELOG.md` --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ec25dba..db66fdfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - **Breaking Change** パースの都合によりmatch文の構文を変更。パターンの前に`case`キーワードが必要となり、`*`は`default`に変更。 - **Breaking Change** 多くの予約語を追加。これまで変数名等に使えていた名前に影響が出る可能性があります。 - **Breaking Change** 配列及び関数の引数において、空白区切りが使用できなくなりました。`,`または改行が必要です。 -- **Breaking Change** `Math:rnd`が範囲外の値を返す可能性があるのを修正。 +- Fix: `Math:rnd`が範囲外の値を返す可能性があるのを修正。 # 0.17.0 - `package.json`を修正 From af24c7180b672ae446003388f5b43d8a40175dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:34:29 +0900 Subject: [PATCH 13/24] Updated `std-math.md` --- docs/std-math.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/std-math.md b/docs/std-math.md index 32ce40eb..dc32cf75 100644 --- a/docs/std-math.md +++ b/docs/std-math.md @@ -120,13 +120,19 @@ _x_ +1の自然対数を計算します。 _min_ および _max_ を渡した場合、_min_ <= x, x <= _max_ の整数、 渡していない場合は 0 <= x, x < 1 の 小数が返されます。 -### @Math:gen_rng(_seed_: num | str): fn +### @Math:gen_rng(_seed_: num | str, _algorithm_?: str): @(_min_?: num, _max_?: num) シードから乱数生成機を生成します。 -生成された乱数生成器は 0 <= x, x < 1 の 小数を返します。 - -### @Math:gen_rng_unbiased(_seed_: num | str): @(_min_: num, _max_: num) -シードから _min_ <= x, x <= _max_ の整数を一様分布で生成する乱数生成機を生成します。 -_min_ および _max_ が渡されていない場合はエラーとなります。 +生成された乱数生成器は、_min_ および _max_ を渡した場合、_min_ <= x, x <= _max_ の整数、 +渡していない場合は 0 <= x, x < 1 の浮動小数点数を返します。 +_algorithm_ の指定による挙動の変化は下記の通りです。 +| _algorithm_ の指定 | 内部の乱数生成アルゴリズム | 範囲指定整数生成アルゴリズム | +|--|--|--| +| `rc4` | RC4 | Rejection Sampling | +| `rc4_legacy` | RC4 | 浮動小数点数演算による範囲制限(0.18.0以前のアルゴリズム) | +| 無指定 または 上記以外の任意の文字列 | ChaCha20 | Rejection Sampling | + +> [!CAUTION] +> `rc4_legacy`等、浮動小数点数演算を伴う範囲指定整数生成アルゴリズムでは、演算時の丸め誤差により、指定した _max_ の値より大きな値が生成される可能性があります。 ## その他 ### @Math:clz32(_x_: num): num From 3feb937b91ff993b9d8ed405ae7efdc1eaad2ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:01:59 +0900 Subject: [PATCH 14/24] Updated `package-lock.json` --- package-lock.json | 499 ++++++---------------------------------------- 1 file changed, 58 insertions(+), 441 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ab6004c..840e13a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.19.0", "license": "MIT", "dependencies": { + "@types/libsodium-wrappers-sumo": "0.7.8", + "libsodium-wrappers-sumo": "0.7.13", "seedrandom": "3.0.5", "stringz": "2.1.0", "uuid": "9.0.1" @@ -22,11 +24,9 @@ "@typescript-eslint/eslint-plugin": "7.1.1", "@typescript-eslint/parser": "7.1.1", "chalk": "5.3.0", - "copyfiles": "2.4.1", "eslint": "8.57.0", "eslint-plugin-import": "2.29.1", "jest": "29.7.0", - "peggy": "4.0.2", "semver": "7.6.2", "ts-jest": "29.1.2", "ts-jest-resolver": "2.0.1", @@ -1586,33 +1586,6 @@ "node": ">= 8" } }, - "node_modules/@peggyjs/from-mem": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@peggyjs/from-mem/-/from-mem-1.2.1.tgz", - "integrity": "sha512-qh5zG8WKT36142/FqOYtpF0scRR3ZJ3H5XST1bJ/KV2FvyB5MvUB/tB9ZjihRe1iKjJD4PBOZczzwEx7hJtgMw==", - "dev": true, - "dependencies": { - "semver": "7.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@peggyjs/from-mem/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@rushstack/node-core-library": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz", @@ -1880,6 +1853,21 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/libsodium-wrappers": { + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.14.tgz", + "integrity": "sha512-5Kv68fXuXK0iDuUir1WPGw2R9fOZUlYlSAa0ztMcL0s0BfIDTqg9GXz8K30VJpPP3sxWhbolnQma2x+/TfkzDQ==", + "license": "MIT" + }, + "node_modules/@types/libsodium-wrappers-sumo": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.8.tgz", + "integrity": "sha512-N2+df4MB/A+W0RAcTw7A5oxKgzD+Vh6Ye7lfjWIi5SdTzVLfHPzxUjhwPqHLO5Ev9fv/+VHl+sUaUuTg4fUPqw==", + "license": "MIT", + "dependencies": { + "@types/libsodium-wrappers": "*" + } + }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -2732,17 +2720,6 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -2795,31 +2772,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/copyfiles": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", - "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", - "dev": true, - "dependencies": { - "glob": "^7.0.5", - "minimatch": "^3.0.3", - "mkdirp": "^1.0.4", - "noms": "0.0.0", - "through2": "^2.0.1", - "untildify": "^4.0.0", - "yargs": "^16.1.0" - }, - "bin": { - "copyfiles": "copyfiles", - "copyup": "copyfiles" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -4619,12 +4571,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6154,6 +6100,21 @@ "node": ">= 0.8.0" } }, + "node_modules/libsodium-sumo": { + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.14.tgz", + "integrity": "sha512-2nDge6qlAjcwyslAhWfVumlkeSNK5+WCfKa2/VEq9prvlT5vP2FR0m0o5hmKaYqfsZ4TQVj5czQsimZvXDB1CQ==", + "license": "ISC" + }, + "node_modules/libsodium-wrappers-sumo": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.13.tgz", + "integrity": "sha512-lz4YdplzDRh6AhnLGF2Dj2IUj94xRN6Bh8T0HLNwzYGwPehQJX6c7iYVrFUPZ3QqxE0bqC+K0IIqqZJYWumwSQ==", + "license": "ISC", + "dependencies": { + "libsodium-sumo": "^0.7.13" + } + }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -6439,17 +6400,6 @@ "node": ">= 6" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6474,16 +6424,6 @@ "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", "dev": true }, - "node_modules/noms": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", - "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "~1.0.31" - } - }, "node_modules/normalize-package-data": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", @@ -6747,32 +6687,6 @@ "node": ">=8" } }, - "node_modules/peggy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/peggy/-/peggy-4.0.2.tgz", - "integrity": "sha512-j4cepPgB20V7honmTAI+7U022y/n/wVi7Rbbd2QrMl2nifFECpngvA6Zhrz/JdmZ5LIBoTdkgHcDzzaA6C4ABg==", - "dev": true, - "dependencies": { - "@peggyjs/from-mem": "1.2.1", - "commander": "^12.0.0", - "source-map-generator": "0.8.0" - }, - "bin": { - "peggy": "bin/peggy.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/peggy/node_modules/commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -6850,12 +6764,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -7006,18 +6914,6 @@ "node": ">=8" } }, - "node_modules/readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -7184,12 +7080,6 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -7284,15 +7174,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-generator": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/source-map-generator/-/source-map-generator-0.8.0.tgz", - "integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -7353,12 +7234,6 @@ "node": ">=10" } }, - "node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -7558,46 +7433,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/through2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -7945,15 +7780,6 @@ "node": ">= 4.0.0" } }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -7989,12 +7815,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -8179,15 +7999,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -8203,24 +8014,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", @@ -9467,26 +9260,6 @@ "fastq": "^1.6.0" } }, - "@peggyjs/from-mem": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@peggyjs/from-mem/-/from-mem-1.2.1.tgz", - "integrity": "sha512-qh5zG8WKT36142/FqOYtpF0scRR3ZJ3H5XST1bJ/KV2FvyB5MvUB/tB9ZjihRe1iKjJD4PBOZczzwEx7hJtgMw==", - "dev": true, - "requires": { - "semver": "7.6.0" - }, - "dependencies": { - "semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "@rushstack/node-core-library": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz", @@ -9727,6 +9500,19 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/libsodium-wrappers": { + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.14.tgz", + "integrity": "sha512-5Kv68fXuXK0iDuUir1WPGw2R9fOZUlYlSAa0ztMcL0s0BfIDTqg9GXz8K30VJpPP3sxWhbolnQma2x+/TfkzDQ==" + }, + "@types/libsodium-wrappers-sumo": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.8.tgz", + "integrity": "sha512-N2+df4MB/A+W0RAcTw7A5oxKgzD+Vh6Ye7lfjWIi5SdTzVLfHPzxUjhwPqHLO5Ev9fv/+VHl+sUaUuTg4fUPqw==", + "requires": { + "@types/libsodium-wrappers": "*" + } + }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -10305,17 +10091,6 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -10360,27 +10135,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "copyfiles": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", - "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", - "dev": true, - "requires": { - "glob": "^7.0.5", - "minimatch": "^3.0.3", - "mkdirp": "^1.0.4", - "noms": "0.0.0", - "through2": "^2.0.1", - "untildify": "^4.0.0", - "yargs": "^16.1.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, "create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -11671,12 +11425,6 @@ "call-bind": "^1.0.2" } }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -12827,6 +12575,19 @@ "type-check": "~0.4.0" } }, + "libsodium-sumo": { + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.14.tgz", + "integrity": "sha512-2nDge6qlAjcwyslAhWfVumlkeSNK5+WCfKa2/VEq9prvlT5vP2FR0m0o5hmKaYqfsZ4TQVj5czQsimZvXDB1CQ==" + }, + "libsodium-wrappers-sumo": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.13.tgz", + "integrity": "sha512-lz4YdplzDRh6AhnLGF2Dj2IUj94xRN6Bh8T0HLNwzYGwPehQJX6c7iYVrFUPZ3QqxE0bqC+K0IIqqZJYWumwSQ==", + "requires": { + "libsodium-sumo": "^0.7.13" + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -13043,10 +12804,6 @@ "kind-of": "^6.0.3" } }, - "mkdirp": { - "version": "1.0.4", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -13071,16 +12828,6 @@ "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", "dev": true }, - "noms": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", - "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "~1.0.31" - } - }, "normalize-package-data": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", @@ -13265,25 +13012,6 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "peggy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/peggy/-/peggy-4.0.2.tgz", - "integrity": "sha512-j4cepPgB20V7honmTAI+7U022y/n/wVi7Rbbd2QrMl2nifFECpngvA6Zhrz/JdmZ5LIBoTdkgHcDzzaA6C4ABg==", - "dev": true, - "requires": { - "@peggyjs/from-mem": "1.2.1", - "commander": "^12.0.0", - "source-map-generator": "0.8.0" - }, - "dependencies": { - "commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "dev": true - } - } - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -13337,12 +13065,6 @@ "react-is": "^18.0.0" } }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -13446,18 +13168,6 @@ } } }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -13567,12 +13277,6 @@ } } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -13641,12 +13345,6 @@ "version": "0.6.1", "dev": true }, - "source-map-generator": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/source-map-generator/-/source-map-generator-0.8.0.tgz", - "integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==", - "dev": true - }, "source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -13704,12 +13402,6 @@ "escape-string-regexp": "^2.0.0" } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, "string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -13854,48 +13546,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -14126,12 +13776,6 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, - "untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true - }, "update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -14151,12 +13795,6 @@ "punycode": "^2.1.0" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, "uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -14298,12 +13936,6 @@ "signal-exit": "^3.0.7" } }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -14316,21 +13948,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, "yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", From d715671934a54db9b90c6126f460255d813d2743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:24:36 +0900 Subject: [PATCH 15/24] * Reverted `CHANGELOG.md` to match upstream --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c69aec..faaca517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,6 @@ - `arr.splice`を追加 - `arr.at`を追加 - For Hosts: エラーハンドラ使用時、InterpreterのオプションでabortOnErrorをtrueにした時のみ全体のabortを行うように -- 関数`Math:gen_rng`に第二引数`algorithm`をオプション引数として追加。 - - アルゴリズムを`chacha20`、`rc4`、`rc4_legacy`から選べるようになりました。 - - **Breaking Change** `algorithm`を指定しない場合、`chacha20`が選択されます。 -- Fix: **Breaking Change** `Math:rnd`が範囲外の値を返す可能性があるのをアルゴリズムの変更により修正。 # 0.18.0 - `Core:abort`でプログラムを緊急停止できるように @@ -40,7 +36,6 @@ - 関数`Str#charcode_at` `Str#to_arr` `Str#to_char_arr` `Str#to_charcode_arr` `Str#to_utf8_byte_arr` `Str#to_unicode_codepoint_arr` `Str:from_unicode_codepoints` `Str:from_utf8_bytes`を追加 - Fix: `Str#codepoint_at`がサロゲートペアに対応していないのを修正 - 配列の範囲外および非整数のインデックスへの代入でエラーを出すように - ## Note バージョン0.16.0に記録漏れがありました。 >- 関数`Str:from_codepoint` `Str#codepoint_at`を追加 From 1ec26b4f5b2a900a95b07893c1cdd1bb441894e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:25:01 +0900 Subject: [PATCH 16/24] Added `random algorithms.md` --- unreleased/random algorithms.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 unreleased/random algorithms.md diff --git a/unreleased/random algorithms.md b/unreleased/random algorithms.md new file mode 100644 index 00000000..4a922fd1 --- /dev/null +++ b/unreleased/random algorithms.md @@ -0,0 +1,4 @@ +- 関数`Math:gen_rng`に第二引数`algorithm`をオプション引数として追加。 + - アルゴリズムを`chacha20`、`rc4`、`rc4_legacy`から選べるようになりました。 + - **Breaking Change** `algorithm`を指定しない場合、`chacha20`が選択されます。 +- Fix: **Breaking Change** `Math:rnd`が範囲外の値を返す可能性があるのをアルゴリズムの変更により修正。 From 6904a1a272ac5c9f813dbefabb26ff7ec3193ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:37:20 +0900 Subject: [PATCH 17/24] Updated std-math.md --- docs/std-math.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/std-math.md b/docs/std-math.md index dc32cf75..54cc1702 100644 --- a/docs/std-math.md +++ b/docs/std-math.md @@ -128,7 +128,7 @@ _algorithm_ の指定による挙動の変化は下記の通りです。 | _algorithm_ の指定 | 内部の乱数生成アルゴリズム | 範囲指定整数生成アルゴリズム | |--|--|--| | `rc4` | RC4 | Rejection Sampling | -| `rc4_legacy` | RC4 | 浮動小数点数演算による範囲制限(0.18.0以前のアルゴリズム) | +| `rc4_legacy` | RC4 | 浮動小数点数演算による範囲制限(0.19.0以前のアルゴリズム) | | 無指定 または 上記以外の任意の文字列 | ChaCha20 | Rejection Sampling | > [!CAUTION] From 22f2be5d9806bfe241e547100d8113a797c21670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:03:39 +0900 Subject: [PATCH 18/24] Added initial support for option objects in `Math:gen_rng` --- docs/std-math.md | 9 +++++---- src/interpreter/lib/std.ts | 13 ++++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/std-math.md b/docs/std-math.md index 54cc1702..a9af7e69 100644 --- a/docs/std-math.md +++ b/docs/std-math.md @@ -120,15 +120,16 @@ _x_ +1の自然対数を計算します。 _min_ および _max_ を渡した場合、_min_ <= x, x <= _max_ の整数、 渡していない場合は 0 <= x, x < 1 の 小数が返されます。 -### @Math:gen_rng(_seed_: num | str, _algorithm_?: str): @(_min_?: num, _max_?: num) +### @Math:gen_rng(_seed_: num | str, _options_?: str | obj): @(_min_?: num, _max_?: num) シードから乱数生成機を生成します。 生成された乱数生成器は、_min_ および _max_ を渡した場合、_min_ <= x, x <= _max_ の整数、 渡していない場合は 0 <= x, x < 1 の浮動小数点数を返します。 -_algorithm_ の指定による挙動の変化は下記の通りです。 -| _algorithm_ の指定 | 内部の乱数生成アルゴリズム | 範囲指定整数生成アルゴリズム | +_options_ に渡した文字列またはオブジェクトを通じて、内部の挙動を指定できます。 +_options_ を文字列で指定した場合や、`options.algorithm`の指定による挙動の変化は下記の通りです。 +| `options.algorithm` | 内部の乱数生成アルゴリズム | 範囲指定整数生成アルゴリズム | |--|--|--| | `rc4` | RC4 | Rejection Sampling | -| `rc4_legacy` | RC4 | 浮動小数点数演算による範囲制限(0.19.0以前のアルゴリズム) | +| `rc4_legacy` | RC4 | 浮動小数点数演算による範囲制限​(0.19.0以前のアルゴリズム) | | 無指定 または 上記以外の任意の文字列 | ChaCha20 | Rejection Sampling | > [!CAUTION] diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 16c93638..cb0fa58e 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -458,11 +458,18 @@ export const std: Record = { return NUM(CryptoGen.instance.generateNumber0To1()); }), - 'Math:gen_rng': FN_NATIVE(async ([seed, algorithm]) => { + 'Math:gen_rng': FN_NATIVE(async ([seed, options]) => { expectAny(seed); - const algo = !algorithm || algorithm.type !== 'str' ? STR('chacha20') : algorithm; + let algo = 'chacha20'; + if (options?.type === 'obj') { + const v = options.value.get('algorithm'); + algo = v?.type === 'str' ? v.value : algo; + } + else if (options?.type === 'str') { + algo = options.value; + } if (seed.type !== 'num' && seed.type !== 'str' && seed.type !== 'null') return NULL; - switch (algo.value) { + switch (algo) { case 'rc4_legacy': return GenerateLegacyRandom(seed); case 'rc4': From c87b6308af05cc7fc89c0746e881a6ca3c2a4052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Fri, 2 Aug 2024 23:57:40 +0900 Subject: [PATCH 19/24] * Changed implementation of ChaCha20 * `Math:gen_rng` no longer accepts `str` options --- package-lock.json | 58 --------------------- package.json | 2 - src/interpreter/lib/std.ts | 10 ++-- src/utils/random/chacha20.ts | 99 +++++++++++++++++++++++++----------- src/utils/random/genrng.ts | 16 ++++-- 5 files changed, 84 insertions(+), 101 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f668b7b..48940b1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,6 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@types/libsodium-wrappers-sumo": "0.7.8", - "libsodium-wrappers-sumo": "0.7.13", "seedrandom": "3.0.5", "stringz": "2.1.0", "uuid": "10.0.0" @@ -1913,21 +1911,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/libsodium-wrappers": { - "version": "0.7.14", - "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.14.tgz", - "integrity": "sha512-5Kv68fXuXK0iDuUir1WPGw2R9fOZUlYlSAa0ztMcL0s0BfIDTqg9GXz8K30VJpPP3sxWhbolnQma2x+/TfkzDQ==", - "license": "MIT" - }, - "node_modules/@types/libsodium-wrappers-sumo": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.8.tgz", - "integrity": "sha512-N2+df4MB/A+W0RAcTw7A5oxKgzD+Vh6Ye7lfjWIi5SdTzVLfHPzxUjhwPqHLO5Ev9fv/+VHl+sUaUuTg4fUPqw==", - "license": "MIT", - "dependencies": { - "@types/libsodium-wrappers": "*" - } - }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -6181,21 +6164,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libsodium-sumo": { - "version": "0.7.14", - "resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.14.tgz", - "integrity": "sha512-2nDge6qlAjcwyslAhWfVumlkeSNK5+WCfKa2/VEq9prvlT5vP2FR0m0o5hmKaYqfsZ4TQVj5czQsimZvXDB1CQ==", - "license": "ISC" - }, - "node_modules/libsodium-wrappers-sumo": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.13.tgz", - "integrity": "sha512-lz4YdplzDRh6AhnLGF2Dj2IUj94xRN6Bh8T0HLNwzYGwPehQJX6c7iYVrFUPZ3QqxE0bqC+K0IIqqZJYWumwSQ==", - "license": "ISC", - "dependencies": { - "libsodium-sumo": "^0.7.13" - } - }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -9590,19 +9558,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/libsodium-wrappers": { - "version": "0.7.14", - "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.14.tgz", - "integrity": "sha512-5Kv68fXuXK0iDuUir1WPGw2R9fOZUlYlSAa0ztMcL0s0BfIDTqg9GXz8K30VJpPP3sxWhbolnQma2x+/TfkzDQ==" - }, - "@types/libsodium-wrappers-sumo": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/@types/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.8.tgz", - "integrity": "sha512-N2+df4MB/A+W0RAcTw7A5oxKgzD+Vh6Ye7lfjWIi5SdTzVLfHPzxUjhwPqHLO5Ev9fv/+VHl+sUaUuTg4fUPqw==", - "requires": { - "@types/libsodium-wrappers": "*" - } - }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -12682,19 +12637,6 @@ "type-check": "~0.4.0" } }, - "libsodium-sumo": { - "version": "0.7.14", - "resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.14.tgz", - "integrity": "sha512-2nDge6qlAjcwyslAhWfVumlkeSNK5+WCfKa2/VEq9prvlT5vP2FR0m0o5hmKaYqfsZ4TQVj5czQsimZvXDB1CQ==" - }, - "libsodium-wrappers-sumo": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.13.tgz", - "integrity": "sha512-lz4YdplzDRh6AhnLGF2Dj2IUj94xRN6Bh8T0HLNwzYGwPehQJX6c7iYVrFUPZ3QqxE0bqC+K0IIqqZJYWumwSQ==", - "requires": { - "libsodium-sumo": "^0.7.13" - } - }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", diff --git a/package.json b/package.json index 1feafccb..2552b63f 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,6 @@ "typescript": "5.5.4" }, "dependencies": { - "@types/libsodium-wrappers-sumo": "0.7.8", - "libsodium-wrappers-sumo": "0.7.13", "seedrandom": "3.0.5", "stringz": "2.1.0", "uuid": "10.0.0" diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index cb0fa58e..ea5df722 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -463,10 +463,8 @@ export const std: Record = { let algo = 'chacha20'; if (options?.type === 'obj') { const v = options.value.get('algorithm'); - algo = v?.type === 'str' ? v.value : algo; - } - else if (options?.type === 'str') { - algo = options.value; + if (v?.type !== 'str') throw new AiScriptRuntimeError('`object.algorithm` must be string.'); + algo = v.value; } if (seed.type !== 'num' && seed.type !== 'str' && seed.type !== 'null') return NULL; switch (algo) { @@ -474,8 +472,10 @@ export const std: Record = { return GenerateLegacyRandom(seed); case 'rc4': return GenerateRC4Random(seed); - default: + case 'chacha20': return GenerateChaCha20Random(seed); + default: + throw new AiScriptRuntimeError('`object.algorithm` must be one of these: `chacha20`, `rc4`, or `rc4_legacy`.'); } }), //#endregion diff --git a/src/utils/random/chacha20.ts b/src/utils/random/chacha20.ts index 733c71df..fe80b2f8 100644 --- a/src/utils/random/chacha20.ts +++ b/src/utils/random/chacha20.ts @@ -1,30 +1,72 @@ -import sodium from 'libsodium-wrappers-sumo'; -import { RandomBase, bigMaxSafeIntegerExclusive, bigSafeIntegerBits, readBigUintLittleEndian, safeIntegerBits } from './randomBase.js'; +import { RandomBase, readBigUintLittleEndian, safeIntegerBits } from './randomBase.js'; +// translated from https://github.com/skeeto/chacha-js/blob/master/chacha.js const chacha20BlockSize = 64; -const bigChacha20BlockSize = BigInt(chacha20BlockSize); - +const CHACHA_ROUNDS = 20; +const CHACHA_KEYSIZE = 32; +const CHACHA_IVSIZE = 8; +function rotate(v: number, n: number): number { return (v << n) | (v >>> (32 - n)); } +function quarterRound(x: Uint32Array, a: number, b: number, c: number, d: number): void { + if (x.length < 16) return; + let va = x[a]; + let vb = x[b]; + let vc = x[c]; + let vd = x[d]; + if (va === undefined || vb === undefined || vc === undefined || vd === undefined) return; + va += vb; + vd = rotate(vd ^ va, 16); + vc += vd; + vb = rotate(vb ^ vc, 12); + va += vb; + vd = rotate(vd ^ va, 8); + vc += vd; + vb = rotate(vb ^ vc, 7); + x[a] = va; + x[b] = vb; + x[c] = vc; + x[d] = vd; +} +function generateChaCha20(state: Uint32Array) : void { + for (let i = 0; i < CHACHA_ROUNDS; i += 2) { + quarterRound(state, 0, 4, 8, 12); + quarterRound(state, 1, 5, 9, 13); + quarterRound(state, 2, 6, 10, 14); + quarterRound(state, 3, 7, 11, 15); + quarterRound(state, 0, 5, 10, 15); + quarterRound(state, 1, 6, 11, 12); + quarterRound(state, 2, 7, 8, 13); + quarterRound(state, 3, 4, 9, 14); + } +} export class ChaCha20 extends RandomBase { - public static ready = sodium.ready; - private key: Uint8Array; - private nonce: Uint8Array; + private keynonce: Uint32Array; + private state: Uint32Array; private buffer: Uint8Array; private filledBuffer: Uint8Array; private counter: bigint; - constructor(seed?: string | number | Uint8Array | undefined) { - const keyNonceBytes = sodium.crypto_stream_chacha20_NONCEBYTES + sodium.crypto_stream_chacha20_KEYBYTES; + constructor(seed?: Uint8Array | undefined) { + const keyNonceBytes = CHACHA_IVSIZE + CHACHA_KEYSIZE; super(); let keynonce: Uint8Array; if (typeof seed === 'undefined') { keynonce = crypto.getRandomValues(new Uint8Array(keyNonceBytes)); - } else if (typeof seed === 'number') { - const array = new Float64Array([seed]); - keynonce = sodium.crypto_generichash(keyNonceBytes, new Uint8Array(array.buffer)); } else { - keynonce = sodium.crypto_generichash(keyNonceBytes, seed); + keynonce = seed; + if (keynonce.byteLength > keyNonceBytes) keynonce = seed.subarray(0, keyNonceBytes); + if (keynonce.byteLength < keyNonceBytes) { + const y = new Uint8Array(keyNonceBytes); + y.set(keynonce); + keynonce = y; + } } - this.key = keynonce.subarray(0, sodium.crypto_stream_chacha20_KEYBYTES); - this.nonce = keynonce.subarray(sodium.crypto_stream_chacha20_KEYBYTES); + const key = keynonce.subarray(0, CHACHA_KEYSIZE); + const nonce = keynonce.subarray(CHACHA_KEYSIZE, CHACHA_KEYSIZE + CHACHA_IVSIZE); + const kn = new Uint8Array(16 * 4); + kn.set([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]); + kn.set(key, 4 * 4); + kn.set(nonce, 14 * 4); + this.keynonce = new Uint32Array(kn.buffer); + this.state = new Uint32Array(16); this.buffer = new Uint8Array(chacha20BlockSize); this.counter = 0n; this.filledBuffer = new Uint8Array(0); @@ -37,28 +79,23 @@ export class ChaCha20 extends RandomBase { private fillBufferDirect(buffer: Uint8Array): Uint8Array { if ((buffer.length % chacha20BlockSize) !== 0) throw new Error('ChaCha20.fillBufferDirect should always be called with the buffer with the length a multiple-of-64!'); buffer.fill(0); - let blocks = BigInt(buffer.length / chacha20BlockSize); - let counter = this.counter % bigMaxSafeIntegerExclusive; - const newCount = counter + blocks; - const overflow = newCount >> bigSafeIntegerBits; - if (overflow === 0n) { - buffer.set(sodium.crypto_stream_chacha20_xor_ic(buffer, this.nonce, Number(counter), this.key)); - this.counter = newCount; - return buffer; - } + let counter = this.counter; + const state = this.state; + const counterState = new BigUint64Array(state.buffer); let dst = buffer; while (dst.length > 0) { - const remainingBlocks = bigMaxSafeIntegerExclusive - counter; - const genBlocks = remainingBlocks > blocks ? blocks : remainingBlocks; - blocks -= genBlocks; - const dbuf = dst.subarray(0, Number(genBlocks * bigChacha20BlockSize)); // safe integers wouldn't lose any precision with multiplying by a power-of-two anyway. - dbuf.set(sodium.crypto_stream_chacha20_xor_ic(dbuf, this.nonce, Number(counter), this.key)); + const dbuf = dst.subarray(0, state.byteLength); + state.set(this.keynonce); + counterState[6] = BigInt.asUintN(64, counter); + generateChaCha20(state); + dbuf.set(new Uint8Array(state.buffer)); dst = dst.subarray(dbuf.length); - counter = BigInt.asUintN(safeIntegerBits, counter + genBlocks); - this.counter = counter; + counter = BigInt.asUintN(safeIntegerBits, counter + 1n); } + this.counter = counter; return buffer; } + protected generateBigUintByBytes(bytes: number): bigint { let u8a = new Uint8Array(Math.ceil(bytes / 8) * 8); if (u8a.length < 1 || !Number.isSafeInteger(bytes)) return 0n; diff --git a/src/utils/random/genrng.ts b/src/utils/random/genrng.ts index 30bed298..f6a6a4ad 100644 --- a/src/utils/random/genrng.ts +++ b/src/utils/random/genrng.ts @@ -1,9 +1,9 @@ import seedrandom from 'seedrandom'; import { FN_NATIVE, NULL, NUM } from '../../interpreter/value.js'; -import { assertNumber } from '../../interpreter/util.js'; import { SeedRandomWrapper } from './seedrandom.js'; -import type { VNativeFn, VNull, Value } from '../../interpreter/value.js'; import { ChaCha20 } from './chacha20.js'; +import type { VNativeFn, VNull, Value } from '../../interpreter/value.js'; +import { textEncoder } from '../../const.js'; export function GenerateLegacyRandom(seed: Value | undefined) : VNativeFn | VNull { if (!seed || seed.type !== 'num' && seed.type !== 'str') return NULL; @@ -28,10 +28,16 @@ export function GenerateRC4Random(seed: Value | undefined) : VNativeFn | VNull { }); } -export async function GenerateChaCha20Random(seed: Value | undefined) : Promise { +export async function GenerateChaCha20Random(seed: Value | undefined) : Promise { if (!seed || seed.type !== 'num' && seed.type !== 'str' && seed.type !== 'null') return NULL; - await ChaCha20.ready; - const rng = new ChaCha20(seed.type === 'null' ? undefined : seed.value); + let actualSeed : Uint8Array | undefined = undefined; + if (seed.type === 'num') + { + actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', new Uint8Array(new Float64Array([seed.value])))); + } else if (seed.type === 'str') { + actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', new Uint8Array(textEncoder.encode(seed.value)))); + } + const rng = new ChaCha20(actualSeed); return FN_NATIVE(([min, max]) => { if (min && min.type === 'num' && max && max.type === 'num') { const result = rng.generateRandomIntegerInRange(min.value, max.value); From 96375cac6248752e8842b7f51ea71de94cc58222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Fri, 2 Aug 2024 23:59:05 +0900 Subject: [PATCH 20/24] Updated `std-math.md` --- docs/std-math.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/std-math.md b/docs/std-math.md index a9af7e69..53e4f897 100644 --- a/docs/std-math.md +++ b/docs/std-math.md @@ -120,12 +120,12 @@ _x_ +1の自然対数を計算します。 _min_ および _max_ を渡した場合、_min_ <= x, x <= _max_ の整数、 渡していない場合は 0 <= x, x < 1 の 小数が返されます。 -### @Math:gen_rng(_seed_: num | str, _options_?: str | obj): @(_min_?: num, _max_?: num) +### @Math:gen_rng(_seed_: num | str, _options_?: obj): @(_min_?: num, _max_?: num) シードから乱数生成機を生成します。 生成された乱数生成器は、_min_ および _max_ を渡した場合、_min_ <= x, x <= _max_ の整数、 渡していない場合は 0 <= x, x < 1 の浮動小数点数を返します。 -_options_ に渡した文字列またはオブジェクトを通じて、内部の挙動を指定できます。 -_options_ を文字列で指定した場合や、`options.algorithm`の指定による挙動の変化は下記の通りです。 +_options_ に渡したオブジェクトを通じて、内部の挙動を指定できます。 +`options.algorithm`の指定による挙動の変化は下記の通りです。 | `options.algorithm` | 内部の乱数生成アルゴリズム | 範囲指定整数生成アルゴリズム | |--|--|--| | `rc4` | RC4 | Rejection Sampling | From 556839a88189c5ddb5ce22d0238f3edaae6fe5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sat, 3 Aug 2024 00:02:04 +0900 Subject: [PATCH 21/24] Updated `std-math.md` --- docs/std-math.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/std-math.md b/docs/std-math.md index 53e4f897..ffa2f845 100644 --- a/docs/std-math.md +++ b/docs/std-math.md @@ -130,7 +130,7 @@ _options_ に渡したオブジェクトを通じて、内部の挙動を指定 |--|--|--| | `rc4` | RC4 | Rejection Sampling | | `rc4_legacy` | RC4 | 浮動小数点数演算による範囲制限​(0.19.0以前のアルゴリズム) | -| 無指定 または 上記以外の任意の文字列 | ChaCha20 | Rejection Sampling | +| 無指定 または 'chacha20' | ChaCha20 | Rejection Sampling | > [!CAUTION] > `rc4_legacy`等、浮動小数点数演算を伴う範囲指定整数生成アルゴリズムでは、演算時の丸め誤差により、指定した _max_ の値より大きな値が生成される可能性があります。 From fb29ab40ce0d68c5f070d6420a5a37c73cfff255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sat, 3 Aug 2024 00:04:25 +0900 Subject: [PATCH 22/24] Fixed `Math:gen_rng` returned `Promise` instead of `VNativeFn | VNull` --- src/interpreter/lib/std.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index ea5df722..9539a67d 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -473,7 +473,7 @@ export const std: Record = { case 'rc4': return GenerateRC4Random(seed); case 'chacha20': - return GenerateChaCha20Random(seed); + return await GenerateChaCha20Random(seed); default: throw new AiScriptRuntimeError('`object.algorithm` must be one of these: `chacha20`, `rc4`, or `rc4_legacy`.'); } From 36e3fef9b93b1b700bbcc7cf01e4580a193f069a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:03:24 +0900 Subject: [PATCH 23/24] * Fixed ChaCha20 generating wrong values * Fixed potential overflow in ChaCha20 --- src/utils/random/chacha20.ts | 43 ++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/utils/random/chacha20.ts b/src/utils/random/chacha20.ts index fe80b2f8..631777fd 100644 --- a/src/utils/random/chacha20.ts +++ b/src/utils/random/chacha20.ts @@ -1,4 +1,4 @@ -import { RandomBase, readBigUintLittleEndian, safeIntegerBits } from './randomBase.js'; +import { RandomBase, readBigUintLittleEndian } from './randomBase.js'; // translated from https://github.com/skeeto/chacha-js/blob/master/chacha.js const chacha20BlockSize = 64; @@ -13,29 +13,38 @@ function quarterRound(x: Uint32Array, a: number, b: number, c: number, d: number let vc = x[c]; let vd = x[d]; if (va === undefined || vb === undefined || vc === undefined || vd === undefined) return; - va += vb; + va = (va + vb) | 0; vd = rotate(vd ^ va, 16); - vc += vd; + vc = (vc + vd) | 0; vb = rotate(vb ^ vc, 12); - va += vb; + va = (va + vb) | 0; vd = rotate(vd ^ va, 8); - vc += vd; + vc = (vc + vd) | 0; vb = rotate(vb ^ vc, 7); x[a] = va; x[b] = vb; x[c] = vc; x[d] = vd; } -function generateChaCha20(state: Uint32Array) : void { +function generateChaCha20(dst: Uint32Array, state: Uint32Array) : void { + if (dst.length < 16 || state.length < 16) return; + dst.set(state); for (let i = 0; i < CHACHA_ROUNDS; i += 2) { - quarterRound(state, 0, 4, 8, 12); - quarterRound(state, 1, 5, 9, 13); - quarterRound(state, 2, 6, 10, 14); - quarterRound(state, 3, 7, 11, 15); - quarterRound(state, 0, 5, 10, 15); - quarterRound(state, 1, 6, 11, 12); - quarterRound(state, 2, 7, 8, 13); - quarterRound(state, 3, 4, 9, 14); + quarterRound(dst, 0, 4, 8, 12); + quarterRound(dst, 1, 5, 9, 13); + quarterRound(dst, 2, 6, 10, 14); + quarterRound(dst, 3, 7, 11, 15); + quarterRound(dst, 0, 5, 10, 15); + quarterRound(dst, 1, 6, 11, 12); + quarterRound(dst, 2, 7, 8, 13); + quarterRound(dst, 3, 4, 9, 14); + } + for (let i = 0; i < 16; i++) { + let d = dst[i]; + const s = state[i]; + if (d === undefined || s === undefined) throw new Error('generateChaCha20: Something went wrong!'); + d = (d + s) | 0; + dst[i] = d; } } export class ChaCha20 extends RandomBase { @@ -85,12 +94,12 @@ export class ChaCha20 extends RandomBase { let dst = buffer; while (dst.length > 0) { const dbuf = dst.subarray(0, state.byteLength); + const dst32 = new Uint32Array(dbuf.buffer); state.set(this.keynonce); counterState[6] = BigInt.asUintN(64, counter); - generateChaCha20(state); - dbuf.set(new Uint8Array(state.buffer)); + generateChaCha20(dst32, state); dst = dst.subarray(dbuf.length); - counter = BigInt.asUintN(safeIntegerBits, counter + 1n); + counter = BigInt.asUintN(64, counter + 1n); } this.counter = counter; return buffer; From 452d3533ebf17a7140db3dbabd475f1af9a16541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:48:45 +0900 Subject: [PATCH 24/24] * `Math:gen_rng`: `options` no longer accepts anything but `obj` or `undefined` * Invalid type for `seed` now throws exception in `Math:gen_rng` --- src/interpreter/lib/std.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 9539a67d..389be3f7 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -463,10 +463,13 @@ export const std: Record = { let algo = 'chacha20'; if (options?.type === 'obj') { const v = options.value.get('algorithm'); - if (v?.type !== 'str') throw new AiScriptRuntimeError('`object.algorithm` must be string.'); + if (v?.type !== 'str') throw new AiScriptRuntimeError('`options.algorithm` must be string.'); algo = v.value; } - if (seed.type !== 'num' && seed.type !== 'str' && seed.type !== 'null') return NULL; + else if (options?.type !== undefined) { + throw new AiScriptRuntimeError('`options` must be an object if specified.'); + } + if (seed.type !== 'num' && seed.type !== 'str' && seed.type !== 'null') throw new AiScriptRuntimeError('`seed` must be either number or string if specified.'); switch (algo) { case 'rc4_legacy': return GenerateLegacyRandom(seed); @@ -475,7 +478,7 @@ export const std: Record = { case 'chacha20': return await GenerateChaCha20Random(seed); default: - throw new AiScriptRuntimeError('`object.algorithm` must be one of these: `chacha20`, `rc4`, or `rc4_legacy`.'); + throw new AiScriptRuntimeError('`options.algorithm` must be one of these: `chacha20`, `rc4`, or `rc4_legacy`.'); } }), //#endregion