Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ history.

## Unreleased

- **BREAKING CHANGES:** `fjs/bnf/token_symbol` migrates from authored
TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting
the `Encoding<T>` type into a sibling `types.ts` — importers must use
the `.f.mjs` specifier for runtime values and the `types.ts`
specifier for types
[#1486](https://github.com/functionalscript/functionalscript/pull/1486)
- **BREAKING CHANGES:** `fjs/crypto/vdf` migrates from authored
TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting
the `Sloth` type into a sibling `types.ts` — importers must use the
`.f.mjs` specifier for runtime values and the `types.ts` specifier
for types
[#1486](https://github.com/functionalscript/functionalscript/pull/1486)
- **BREAKING CHANGES:** `fjs/bnf/module.f.ts` migrates from authored
TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting
its type-level API into a sibling `types.ts` — importers must use the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
* symbol of their own. Names are registered as one fixed alphabet and get a
* symbol from their position in it, above the Unicode range and below `eof`.
*
* See `./types.ts` for the `Encoding<T>` type-level API.
*
* @module
*/
import { assert } from '../../asserts/module.f.mjs'
import type { Nullable } from '../../types/nullable/types.ts'
import { fromUndefined } from '../../types/nullable/module.f.mjs'
import { eof, rangeDecode, unicodeRange } from '../module.f.mjs'
/** @import { Encoding } from './types.ts' */

const [, unicodeLast] = rangeDecode(unicodeRange)

Expand All @@ -27,30 +29,10 @@ const start = unicodeLast + 1
/**
* How many names one encoding holds: every symbol from {@link start} up to but
* not including `eof` (`0xFFFFFF`, the top of the 24-bit symbol space).
*
* @type {number}
*/
export const capacity: number = eofSymbol - start

/**
* A bidirectional map between a fixed alphabet of token names and the symbol
* range reserved for them.
*/
export type Encoding<T extends string> = {
/**
* The input symbol standing for `name`.
*
* The result is a bare symbol, the form a tokenizer emits. Wrap it in
* `oneEncode` to use it as a terminal of a grammar rule — a symbol and a
* `TerminalRange` are both plain numbers, so passing one where the other
* belongs is not a type error.
*/
readonly encode: (name: T) => number
/**
* The name a symbol stands for, or `null` when the symbol belongs to no
* registered name — a code point, `eof`, or a symbol past the end of the
* alphabet.
*/
readonly decode: (symbol: number) => Nullable<T>
}
export const capacity = eofSymbol - start

/**
* Builds an encoding over the complete list of token names.
Expand All @@ -61,8 +43,10 @@ export type Encoding<T extends string> = {
*
* @throws When `names` holds more than {@link capacity} entries, or when a name
* repeats — a repeated name has no single symbol to decode back to.
*
* @type {<T extends string>(names: readonly T[]) => Encoding<T>}
*/
export const encoding = <T extends string>(names: readonly T[]): Encoding<T> => {
export const encoding = names => {
assert(names.length <= capacity, ['too many token names', names.length])
assert(new Set(names).size === names.length, ['duplicate token name', names])
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { assertEq } from '../../asserts/module.f.mjs'
import { capacity, encoding } from './module.f.ts'
import { capacity, encoding } from './module.f.mjs'

const names = ['>>', '>>>=', 'instanceof'] as const
const names = /** @type {const} */ (['>>', '>>>=', 'instanceof'])

export const proof = {
encode: () => {
Expand Down Expand Up @@ -30,10 +30,14 @@ export const proof = {
}
},
throw: {
duplicateName: () => { encoding(['a', 'b', 'a'] as const) },
unregisteredName: () => { encoding<string>(['a']).encode('b') },
duplicateName: () => { encoding(['a', 'b', 'a']) },
unregisteredName: () => {
/** @type {readonly string[]} */
const names = ['a']
encoding(names).encode('b')
},
// `capacity` names fit, so one more is the smallest list that doesn't.
// The array is sparse, so this costs a length, not the strings.
tooManyNames: () => { encoding(new Array<string>(capacity + 1)) },
tooManyNames: () => { encoding(new Array(capacity + 1)) },
},
}
29 changes: 29 additions & 0 deletions fjs/bnf/token_symbol/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Types for encoding multi-character token names as single BNF input symbols.
*
* @module
*/

import type { Nullable } from '../../types/nullable/types.ts'

/**
* A bidirectional map between a fixed alphabet of token names and the symbol
* range reserved for them.
*/
export type Encoding<T extends string> = {
/**
* The input symbol standing for `name`.
*
* The result is a bare symbol, the form a tokenizer emits. Wrap it in
* `oneEncode` to use it as a terminal of a grammar rule — a symbol and a
* `TerminalRange` are both plain numbers, so passing one where the other
* belongs is not a type error.
*/
readonly encode: (name: T) => number
/**
* The name a symbol stands for, or `null` when the symbol belongs to no
* registered name — a code point, `eof`, or a symbol past the end of the
* alphabet.
*/
readonly decode: (symbol: number) => Nullable<T>
}
2 changes: 1 addition & 1 deletion fjs/crypto/vdf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ Hex string wrappers belong in caller code, not this module.

## Test vectors

Proofs in `proof.f.ts` pin `sloth.eval(steps)(x)` for several `(x, steps)` pairs on
Proofs in `proof.f.mjs` pin `sloth.eval(steps)(x)` for several `(x, steps)` pairs on
{@link p}. Values match the reference Sloth implementations linked above (pulsar and
dignity.js use the same modulus and algorithm).
48 changes: 23 additions & 25 deletions fjs/crypto/vdf/module.f.ts → fjs/crypto/vdf/module.f.mjs
Original file line number Diff line number Diff line change
@@ -1,42 +1,33 @@
/**
* Sloth verifiable delay function over a fixed 3072-bit safe prime.
*
* See `./types.ts` for the `Sloth` type-level API.
*
* @module
*
* @example
*
* ```ts
* import { sloth } from './module.f.ts'
* ```js
* import { sloth } from './module.f.mjs'
*
* const steps = 4n
* const x = 42n
* const y = sloth.eval(steps)(x)
* if (y === null || !sloth.verify(steps)(x)(y)) { throw y }
* ```
*/
import type { PrimeField } from '../../types/prime_field/types.ts'
import { modSqrt, prime_field } from '../../types/prime_field/module.f.mjs'
import type { Nullable } from '../../types/nullable/types.ts'
import type { Unary } from '../../types/bigint/types.ts'
/** @import { PrimeField } from '../../types/prime_field/types.ts' */
/** @import { Nullable } from '../../types/nullable/types.ts' */
/** @import { Unary } from '../../types/bigint/types.ts' */
/** @import { Sloth } from './types.ts' */

/** Sloth VDF modulus (3072-bit safe prime, same as reference implementations). */
export const p =
0xf2346eae06a23388_2814ff16f6a076d3_b8f2161c5c92171c_0b7b84eed4e9475b_cce0c13bde34512a_fdf90f41ab9b86dc_f834f85e04b27fad_ee712eed23a1d4e5_8cd1b09d9bfb1069_6d614f119179a40c_49dc8762edc29e81_15263913237e1471_8cbcd4dc6b35bace_13f8cdb1b5156c50_c47b4aaee0820c87_4e2864cb854367c3n

/**
* Sloth VDF over prime `modulus` (`p ≡ 3 (mod 4)`).
*/
export type Sloth = {
readonly p: bigint
readonly quadRes: (x: bigint) => boolean
readonly modSqrt: (x: bigint) => bigint
/** Sequential Sloth permutation; `null` when `steps < 0`. */
readonly eval: (steps: bigint) => (x: bigint) => Nullable<bigint>
/** Fast verification of {@link Sloth.eval}; `false` when `steps < 0`. */
readonly verify: (steps: bigint) => (x: bigint) => (y: bigint) => boolean
}

const repeatSeq = (steps: bigint) => (f: Unary) => (value: bigint): bigint => {
/** @type {(steps: bigint) => (f: Unary) => (value: bigint) => bigint} */
const repeatSeq = steps => f => value => {
let v = value
let i = 0n
while (i < steps) {
Expand All @@ -48,22 +39,29 @@ const repeatSeq = (steps: bigint) => (f: Unary) => (value: bigint): bigint => {

/**
* Builds Sloth VDF operations over `modulus`.
*
* @type {(modulus: bigint) => Sloth}
*/
export const sloth_vdf = (modulus: bigint): Sloth => {
const field: PrimeField = prime_field(modulus)
export const sloth_vdf = modulus => {
/** @type {PrimeField} */
const field = prime_field(modulus)
const { neg, pow2, reduce, quadRes } = field
const root = modSqrt(field)

const squareLoop = (steps: bigint) => (value: bigint): bigint =>
/** @type {(steps: bigint) => (value: bigint) => bigint} */
const squareLoop = steps => value =>
repeatSeq(steps)(pow2)(reduce(value))

const modSqrtLoop = (steps: bigint) => (value: bigint): bigint =>
/** @type {(steps: bigint) => (value: bigint) => bigint} */
const modSqrtLoop = steps => value =>
repeatSeq(steps)(root)(reduce(value))

const evalSteps = (steps: bigint) => (x: bigint): Nullable<bigint> =>
/** @type {(steps: bigint) => (x: bigint) => Nullable<bigint>} */
const evalSteps = steps => x =>
steps < 0n ? null : modSqrtLoop(steps)(x)

const verifySteps = (steps: bigint) => (x: bigint) => (y: bigint): boolean => {
/** @type {(steps: bigint) => (x: bigint) => (y: bigint) => boolean} */
const verifySteps = steps => x => y => {
if (steps < 0n) {
return false
}
Expand Down
2 changes: 1 addition & 1 deletion fjs/crypto/vdf/proof.f.ts → fjs/crypto/vdf/proof.f.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sloth, p } from './module.f.ts'
import { sloth, p } from './module.f.mjs'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update the VDF README for the renamed proof

When this proof is renamed to .f.mjs, fjs/crypto/vdf/README.md:28 still directs readers to proof.f.ts. A repo-wide filename search confirms that path no longer exists in this directory, so the test-vector documentation should be updated to reference proof.f.mjs as part of the rename.

Useful? React with 👍 / 👎.

import { assert, assertEq } from '../../asserts/module.f.mjs'

const { eval: evalVdf, verify, modSqrt, quadRes } = sloth
Expand Down
6 changes: 3 additions & 3 deletions fjs/crypto/vdf/todo/iterate-combinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## Problem

`fjs/crypto/vdf/module.f.ts:38-46` defines a fully generic "apply `f` to a
`fjs/crypto/vdf/module.f.mjs:29-37` defines a fully generic "apply `f` to a
value `n` times" combinator inside the VDF module:

```ts
Expand All @@ -20,7 +20,7 @@ const repeatSeq = (steps: bigint) => (f: Unary) => (value: bigint): bigint => {
}
```

It is used twice (`fjs/crypto/vdf/module.f.ts:56-60`), differing only in the
It is used twice (`fjs/crypto/vdf/module.f.mjs:51-57`), differing only in the
iterated function:

```ts
Expand Down Expand Up @@ -62,7 +62,7 @@ distinct from the module that holds it.
## Tasks

- [ ] Add `iterate` to `fjs/types/function/module.f.mjs` with proof coverage.
- [ ] Replace `repeatSeq` in `fjs/crypto/vdf/module.f.ts` with it.
- [ ] Replace `repeatSeq` in `fjs/crypto/vdf/module.f.mjs` with it.
- [ ] Run `npx tsc` and `fjs t`.

## Related
Expand Down
20 changes: 20 additions & 0 deletions fjs/crypto/vdf/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Types for the Sloth verifiable delay function.
*
* @module
*/

import type { Nullable } from '../../types/nullable/types.ts'

/**
* Sloth VDF over prime `modulus` (`p ≡ 3 (mod 4)`).
*/
export type Sloth = {
readonly p: bigint
readonly quadRes: (x: bigint) => boolean
readonly modSqrt: (x: bigint) => bigint
/** Sequential Sloth permutation; `null` when `steps < 0`. */
readonly eval: (steps: bigint) => (x: bigint) => Nullable<bigint>
/** Fast verification of {@link Sloth.eval}; `false` when `steps < 0`. */
readonly verify: (steps: bigint) => (x: bigint) => (y: bigint) => boolean
}
Loading