Skip to content

Commit 171301c

Browse files
Fix: Math:gen_rngseednumを与え、options.algorithmchacha20を指定した或いは何も指定しなかった場合、seed & 255を内部的にseedとしてしまう問題を修正。 (#1019)
* * Fixed ChaCha20 could only generate 256 different seeds for number seed values. * Added `chacha20NumberSeedLegacyBehaviour` to restore the legacy behaviour (`false` by default) * Added test for new ChaCha20 seed generator behaviour. * Lint fixes * Added `chacha20-seed-unexpected-rounding-fix.md`. * Updated `chacha20-seed-unexpected-rounding-fix.md` * Update unreleased/chacha20-seed-unexpected-rounding-fix.md Co-authored-by: Take-John <takejohn@takejohn.jp> * Renamed `chacha20NumberSeedLegacyBehaviour` to `chacha20_number_seed_legacy_behavior` * Update unreleased/chacha20-seed-unexpected-rounding-fix.md Co-authored-by: Take-John <takejohn@takejohn.jp> --------- Co-authored-by: Take-John <takejohn@takejohn.jp>
1 parent b39ac83 commit 171301c

File tree

4 files changed

+47
-9
lines changed

4 files changed

+47
-9
lines changed

src/interpreter/lib/math.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ export const stdMath: Record<`Math:${string}`, Value> = {
228228
}
229229
case 'chacha20': {
230230
if (!isSecureContext) throw new AiScriptRuntimeError(`The random algorithm ${algo} cannot be used because \`crypto.subtle\` is not available. Maybe in non-secure context?`);
231-
return await GenerateChaCha20Random(seed);
231+
return await GenerateChaCha20Random(seed, options?.value);
232232
}
233233
default:
234234
throw new AiScriptRuntimeError('`options.algorithm` must be one of these: `chacha20`, `rc4`, or `rc4_legacy`.');

src/utils/random/genrng.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FN_NATIVE, NULL, NUM } from '../../interpreter/value.js';
33
import { textEncoder } from '../../const.js';
44
import { SeedRandomWrapper } from './seedrandom.js';
55
import { ChaCha20 } from './chacha20.js';
6-
import type { VNativeFn, VNum, VStr } from '../../interpreter/value.js';
6+
import type { Value, VNativeFn, VNum, VStr } from '../../interpreter/value.js';
77

88
export function GenerateLegacyRandom(seed: VNum | VStr): VNativeFn {
99
const rng = seedrandom(seed.value.toString());
@@ -26,11 +26,17 @@ export function GenerateRC4Random(seed: VNum | VStr): VNativeFn {
2626
});
2727
}
2828

29-
export async function GenerateChaCha20Random(seed: VNum | VStr): Promise<VNativeFn> {
29+
export async function GenerateChaCha20Random(seed: VNum | VStr, options: Map<string, Value> | undefined): Promise<VNativeFn> {
3030
let actualSeed: Uint8Array;
31-
if (seed.type === 'num')
32-
{
33-
actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', new Uint8Array(new Float64Array([seed.value]))));
31+
if (seed.type === 'num') {
32+
const float64Array = new Float64Array([seed.value]);
33+
const numberAsIntegerOptionValue = options?.get('chacha20_number_seed_legacy_behavior');
34+
let numberAsInteger = false;
35+
if (numberAsIntegerOptionValue?.type === 'bool') {
36+
numberAsInteger = numberAsIntegerOptionValue.value;
37+
}
38+
const seedToDigest = numberAsInteger ? new Uint8Array(float64Array) : new Uint8Array(float64Array.buffer);
39+
actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', seedToDigest));
3440
} else {
3541
actualSeed = new Uint8Array(await crypto.subtle.digest('SHA-384', new Uint8Array(textEncoder.encode(seed.value))));
3642
}

test/std.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as assert from 'assert';
22
import { describe, expect, test } from 'vitest';
33
import { utils } from '../src';
44
import { AiScriptRuntimeError } from '../src/error';
5-
import { NUM, STR, NULL, ARR, OBJ, BOOL, TRUE, FALSE, ERROR ,FN_NATIVE } from '../src/interpreter/value';
5+
import { NUM, STR, NULL, ARR, OBJ, BOOL, TRUE, FALSE, ERROR, FN_NATIVE } from '../src/interpreter/value';
66
import { exe, eq } from './testutils';
77

88

@@ -88,7 +88,7 @@ describe('Math', () => {
8888
test.concurrent('max', async () => {
8989
eq(await exe("<: Math:max(-2, -3)"), NUM(-2));
9090
});
91-
91+
9292
/* flaky
9393
test.concurrent('rnd', async () => {
9494
const steps = 512;
@@ -158,6 +158,35 @@ describe('Math', () => {
158158
eq(res, ARR([BOOL(true), BOOL(true)]));
159159
});
160160

161+
test.concurrent('gen_rng number seed', async () => {
162+
// 2つのシード値から1~maxの乱数をn回生成して一致率を見る(numがシード値として指定された場合)
163+
const res = await exe(`
164+
@test(seed1, seed2) {
165+
let n = 100
166+
let max = 100000
167+
let threshold = 0.05
168+
let random1 = Math:gen_rng(seed1)
169+
let random2 = Math:gen_rng(seed2)
170+
var same = 0
171+
for n {
172+
if random1(1, max) == random2(1, max) {
173+
same += 1
174+
}
175+
}
176+
let rate = same / n
177+
if seed1 == seed2 { rate == 1 }
178+
else { rate < threshold }
179+
}
180+
let seed1 = 3.0
181+
let seed2 = 3.0000000000000004
182+
<: [
183+
test(seed1, seed1)
184+
test(seed1, seed2)
185+
]
186+
`)
187+
eq(res, ARR([BOOL(true), BOOL(true)]));
188+
});
189+
161190
test.concurrent('gen_rng should reject when null is provided as a seed', async () => {
162191
await expect(() => exe('Math:gen_rng(null)')).rejects.toThrow(AiScriptRuntimeError);
163192
});
@@ -202,7 +231,7 @@ describe('Obj', () => {
202231
203232
<: Obj:merge(o1, o2)
204233
`);
205-
eq(res, utils.jsToVal({ a: 1, b: 3, c: 4}));
234+
eq(res, utils.jsToVal({ a: 1, b: 3, c: 4 }));
206235
});
207236

208237
test.concurrent('pick', async () => {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- Fix: **Breaking Change** `Math:gen_rng``seed``num`を与え、`options.algorithm``chacha20`を指定した或いは何も指定しなかった場合、`seed & 255`を内部的に`seed`としてしまう問題を修正。
2+
- 関数`Math:gen_rng`の`options.chacha20_number_seed_legacy_behavior`に`true`を指定した場合、修正前の動作をする機能を追加(デフォルト:`false`)。
3+
- これらの修正により、同じ`seed`でも修正前と修正後で生成される値が異なるようになります。

0 commit comments

Comments
 (0)