From 3271f2830fd5f5131af5478e06152d7586cfd16d 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, 28 Dec 2025 19:10:20 +0900 Subject: [PATCH 1/5] * Fixed ChaCha20 could only generate 256 different seeds for number seed values. * Added `chacha20NumberSeedLegacyBehaviour` to restore the legacy behaviour (`false` by default) --- src/interpreter/lib/math.ts | 2 +- src/utils/random/genrng.ts | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/interpreter/lib/math.ts b/src/interpreter/lib/math.ts index 691753b0..a4c75d2d 100644 --- a/src/interpreter/lib/math.ts +++ b/src/interpreter/lib/math.ts @@ -228,7 +228,7 @@ export const stdMath: Record<`Math:${string}`, Value> = { } case 'chacha20': { if (!isSecureContext) throw new AiScriptRuntimeError(`The random algorithm ${algo} cannot be used because \`crypto.subtle\` is not available. Maybe in non-secure context?`); - return await GenerateChaCha20Random(seed); + return await GenerateChaCha20Random(seed, options?.value); } default: throw new AiScriptRuntimeError('`options.algorithm` must be one of these: `chacha20`, `rc4`, or `rc4_legacy`.'); diff --git a/src/utils/random/genrng.ts b/src/utils/random/genrng.ts index fff2ee9e..7ab440ba 100644 --- a/src/utils/random/genrng.ts +++ b/src/utils/random/genrng.ts @@ -3,7 +3,7 @@ import { FN_NATIVE, NULL, NUM } from '../../interpreter/value.js'; import { textEncoder } from '../../const.js'; import { SeedRandomWrapper } from './seedrandom.js'; import { ChaCha20 } from './chacha20.js'; -import type { VNativeFn, VNum, VStr } from '../../interpreter/value.js'; +import type { Value, VNativeFn, VNum, VStr } from '../../interpreter/value.js'; export function GenerateLegacyRandom(seed: VNum | VStr): VNativeFn { const rng = seedrandom(seed.value.toString()); @@ -26,11 +26,17 @@ export function GenerateRC4Random(seed: VNum | VStr): VNativeFn { }); } -export async function GenerateChaCha20Random(seed: VNum | VStr): Promise { +export async function GenerateChaCha20Random(seed: VNum | VStr, options: Map | undefined): Promise { let actualSeed: Uint8Array; - if (seed.type === 'num') - { - actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', new Uint8Array(new Float64Array([seed.value])))); + if (seed.type === 'num') { + let float64Array = new Float64Array([seed.value]); + let numberAsIntegerOptionValue = options?.get("chacha20NumberSeedLegacyBehaviour"); + let numberAsInteger = false; + if (numberAsIntegerOptionValue?.type === "bool") { + numberAsInteger = numberAsIntegerOptionValue.value; + } + let seedToDigest = numberAsInteger ? new Uint8Array(float64Array) : new Uint8Array(float64Array.buffer); + actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', seedToDigest)); } else { actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', new Uint8Array(textEncoder.encode(seed.value)))); } From 60bf0121d6294992a31dd556f6e135d3a5e94b35 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, 28 Dec 2025 19:15:54 +0900 Subject: [PATCH 2/5] Added test for new ChaCha20 seed generator behaviour. --- test/std.ts | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/test/std.ts b/test/std.ts index 22598d13..105475dd 100644 --- a/test/std.ts +++ b/test/std.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import { describe, expect, test } from 'vitest'; import { utils } from '../src'; import { AiScriptRuntimeError } from '../src/error'; -import { NUM, STR, NULL, ARR, OBJ, BOOL, TRUE, FALSE, ERROR ,FN_NATIVE } from '../src/interpreter/value'; +import { NUM, STR, NULL, ARR, OBJ, BOOL, TRUE, FALSE, ERROR, FN_NATIVE } from '../src/interpreter/value'; import { exe, eq } from './testutils'; @@ -88,7 +88,7 @@ describe('Math', () => { test.concurrent('max', async () => { eq(await exe("<: Math:max(-2, -3)"), NUM(-2)); }); - + /* flaky test.concurrent('rnd', async () => { const steps = 512; @@ -158,6 +158,35 @@ describe('Math', () => { eq(res, ARR([BOOL(true), BOOL(true)])); }); + test.concurrent('gen_rng number seed', async () => { + // 2つのシード値から1~maxの乱数をn回生成して一致率を見る(numがシード値として指定された場合) + const res = await exe(` + @test(seed1, seed2) { + let n = 100 + let max = 100000 + let threshold = 0.05 + let random1 = Math:gen_rng(seed1) + let random2 = Math:gen_rng(seed2) + var same = 0 + for n { + if random1(1, max) == random2(1, max) { + same += 1 + } + } + let rate = same / n + if seed1 == seed2 { rate == 1 } + else { rate < threshold } + } + let seed1 = 3.0 + let seed2 = 3.0000000000000004 + <: [ + test(seed1, seed1) + test(seed1, seed2) + ] + `) + eq(res, ARR([BOOL(true), BOOL(true)])); + }); + test.concurrent('gen_rng should reject when null is provided as a seed', async () => { await expect(() => exe('Math:gen_rng(null)')).rejects.toThrow(AiScriptRuntimeError); }); @@ -202,7 +231,7 @@ describe('Obj', () => { <: Obj:merge(o1, o2) `); - eq(res, utils.jsToVal({ a: 1, b: 3, c: 4})); + eq(res, utils.jsToVal({ a: 1, b: 3, c: 4 })); }); test.concurrent('pick', async () => { From a833ff0c3655440b7f3f5b33679c9c3b8f307368 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, 28 Dec 2025 19:21:38 +0900 Subject: [PATCH 3/5] Lint fixes --- src/utils/random/genrng.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/random/genrng.ts b/src/utils/random/genrng.ts index 7ab440ba..bb988d02 100644 --- a/src/utils/random/genrng.ts +++ b/src/utils/random/genrng.ts @@ -29,13 +29,13 @@ export function GenerateRC4Random(seed: VNum | VStr): VNativeFn { export async function GenerateChaCha20Random(seed: VNum | VStr, options: Map | undefined): Promise { let actualSeed: Uint8Array; if (seed.type === 'num') { - let float64Array = new Float64Array([seed.value]); - let numberAsIntegerOptionValue = options?.get("chacha20NumberSeedLegacyBehaviour"); + const float64Array = new Float64Array([seed.value]); + const numberAsIntegerOptionValue = options?.get('chacha20NumberSeedLegacyBehaviour'); let numberAsInteger = false; - if (numberAsIntegerOptionValue?.type === "bool") { + if (numberAsIntegerOptionValue?.type === 'bool') { numberAsInteger = numberAsIntegerOptionValue.value; } - let seedToDigest = numberAsInteger ? new Uint8Array(float64Array) : new Uint8Array(float64Array.buffer); + const seedToDigest = numberAsInteger ? new Uint8Array(float64Array) : new Uint8Array(float64Array.buffer); actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', seedToDigest)); } else { actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', new Uint8Array(textEncoder.encode(seed.value)))); From 3ce738148afcf6411e79be6e6826676d54d34642 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, 28 Dec 2025 19:35:42 +0900 Subject: [PATCH 4/5] Added `chacha20-seed-unexpected-rounding-fix.md`. --- unreleased/chacha20-seed-unexpected-rounding-fix.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 unreleased/chacha20-seed-unexpected-rounding-fix.md diff --git a/unreleased/chacha20-seed-unexpected-rounding-fix.md b/unreleased/chacha20-seed-unexpected-rounding-fix.md new file mode 100644 index 00000000..2a2061c6 --- /dev/null +++ b/unreleased/chacha20-seed-unexpected-rounding-fix.md @@ -0,0 +1,3 @@ +- Fix: **Breaking Change** `Math:gen_rng`の`seed`に`num`を与え、`options.algorithm`に`chacha20`を指定した或いは何も指定しなかった場合、`seed`の小数点以下を切り捨てた上で 256 で割った余りを内部的に`seed`としてしまう問題を修正。 + - 関数`Math:gen_rng`の`options.chacha20NumberSeedLegacyBehaviour`に`true`を指定した場合、修正前の動作をする機能を追加(デフォルト:`false`)。 + - これらの修正により、同じ`seed`でも修正前と修正後で生成される値が異なるようになります。 From e6233712287e88b23afd5163efc8d6af61781f73 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, 28 Dec 2025 20:01:54 +0900 Subject: [PATCH 5/5] Updated `chacha20-seed-unexpected-rounding-fix.md` --- unreleased/chacha20-seed-unexpected-rounding-fix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unreleased/chacha20-seed-unexpected-rounding-fix.md b/unreleased/chacha20-seed-unexpected-rounding-fix.md index 2a2061c6..8387aa18 100644 --- a/unreleased/chacha20-seed-unexpected-rounding-fix.md +++ b/unreleased/chacha20-seed-unexpected-rounding-fix.md @@ -1,3 +1,3 @@ -- Fix: **Breaking Change** `Math:gen_rng`の`seed`に`num`を与え、`options.algorithm`に`chacha20`を指定した或いは何も指定しなかった場合、`seed`の小数点以下を切り捨てた上で 256 で割った余りを内部的に`seed`としてしまう問題を修正。 +- Fix: **Breaking Change** `Math:gen_rng`の`seed`に`num`を与え、`options.algorithm`に`chacha20`を指定した或いは何も指定しなかった場合、`seed & 255`を内部的に`seed`としてしまう問題を修正。 - 関数`Math:gen_rng`の`options.chacha20NumberSeedLegacyBehaviour`に`true`を指定した場合、修正前の動作をする機能を追加(デフォルト:`false`)。 - これらの修正により、同じ`seed`でも修正前と修正後で生成される値が異なるようになります。