Compare commits

..
88 Commits
Author SHA1 Message Date
grenadeandClaude Opus 5 4e5b479db4 feat(quantus-crypto): wormhole nullifiers
A deposit to a wormhole address is spent when its nullifier,
poseidon2(poseidon2(salt || secret || transfer_count)), is in
Wormhole::UsedNullifiers. Working out a wormhole balance means computing one
for each deposit. That needs the address's secret, which never leaves WASM.

wormholeNullifiers(mnemonic, password, account, branch, start, addresses,
first, count) returns them for a run of addresses and a run of transfer
counts. The BIP39 seed is stretched once per call; 40 addresses x 256 counts
takes 194 ms in node. A call is capped at 100,000 nullifiers.

This is ported onto qp-poseidon-core, not qp-wormhole-circuit, which would
bring plonky2 into the WASM. The circuit crate is a dev-dependency only, as
the reference: a known-answer test compares the port with
Nullifier::from_preimage across secrets at and above the Goldilocks-prime
limb edge and transfer counts across both 32-bit limbs, and asserts that
enough cases were actually compared rather than skipped.

The doc comments say to check nullifiers against a local copy of the spent
set, never by key: exits publish nullifiers, so a lookup names the exit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 18:13:57 +03:00
grenadeandClaude Opus 5 0b1cfe35b3 feat(quantus-crypto): derive wormhole addresses
A wormhole address is not a key. A path under coin type 189189189 yields a
32-byte secret, and the address is poseidon(poseidon(salt || secret)). Funds
leave only through a ZK proof of that secret. The extension needs the
addresses to show a wallet's wormhole account; it has no use for the secrets.

wormholeAddresses(mnemonic, password, account, branch, start, count) returns
32-byte account ids for m/44'/189189189'/<account>'/<branch>'/<index>', the
mobile wallet's paths. The secrets and first hashes are derived and wiped
inside WASM.

The BIP39 seed is stretched once per call rather than once per address, since
a gap-limit window is dozens of addresses and each stretch is 2048 PBKDF2
rounds. A call is capped at 1000 addresses, and indices must stay below 2^31.

Pinned to the chain node's own vector
(node/src/tests/data/quantus_key_test_data.rs): TEST_MNEMONIC at
m/44'/189189189'/0'/0'/0' is TEST_WORMHOLE_ADDRESS, the same pair the mobile
wallet's SDK tests. Checked in Rust and through the packed package's consumer
test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 15:58:13 +03:00
rob thijssenandClaude Opus 5 e8bf9e20c7 fix(quantus-crypto): strip wasm-bindgen's fetch-based init from the shipped glue
The package could not be bundled. Every webpack consumer failed with:

  Module not found: Error: Can't resolve 'quantus_crypto_bg.wasm'
    in node_modules/@quantus/crypto/generated

wasm-bindgen's async `__wbg_init` contains

  module_or_path = new URL('quantus_crypto_bg.wasm', import.meta.url);

and webpack resolves `new URL(..., import.meta.url)` statically, at build time,
whether or not the branch can run. The file is not in the package — the wasm
ships base64'd in bytes.js, which is the entire point of this package — so the
build failed on a code path we never call.

node never sees it, which is why ten Rust tests, twelve consumer assertions and a
browser probe all passed while the package was unusable in a bundler. It took a
real extension build to surface, and that is the useful lesson: this package's
consumers bundle, and nothing in its own test suite does.

So the dead init is removed after bindgen runs. Shipping a second copy of the
wasm to satisfy a path we do not use would be the wide fix; deleting generated
code we never call is the narrow one.

The stripper asserts the shape it expects and throws if wasm-bindgen changes it,
rather than silently no-opping — a build that quietly stopped stripping would
ship the broken package again. It also re-checks that no reference to the .wasm
filename survives.

Published as 0.1.1.

Refs quantus/wasm#1, quantus/extension#2

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-15 11:53:18 +03:00
rob thijssenandClaude Opus 5 a6d3685c59 refactor(quantus-crypto): drop the @polkadot/wasm-util dependency
It cost more than it saved. Two problems, the second only visible once
quantus/common tried to consume this package:

Its index re-exports packageDetect, whose only job is a side effect registering
with @polkadot/util — a peer dependency inherited for nothing. Deep imports
(/base64, /fflate) avoided that.

But it is a workspace package, so a symlinked consumer resolves its dependencies
through *this* repo's node_modules, where @polkadot/wasm-util points at the
package source rather than its build and carries no exports map. Node follows
symlinks to their realpath, so `@polkadot/wasm-util/base64` failed to resolve
from quantus/common no matter which yarn protocol was used — portal: and link:
behave the same once the realpath is taken.

So: fflate directly for zlib inflate, and fifteen lines for base64 rather than a
dependency at all. Deliberately not atob or Buffer.from — the first is
browser-only, the second node-only, and this runs in an MV3 service worker, a
Worker, node tests and a bundled extension page.

The package is now self-contained apart from fflate, which resolves normally from
any checkout. Size is unchanged at 234,292 raw / 109,649 zlib / 146,200 base64.

Refs quantus/wasm#1, quantus/common#2

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-10 18:39:54 +03:00
rob thijssenandClaude Opus 5 8323e442d9 test(quantus-crypto): browser probes for the constraints node cannot test
cargo test and the consumer test both run in node, which is neither a browser nor
a service worker. Two probes cover the rest.

The automated one is a module Worker served under the exact extension_pages CSP
from both manifests. A module Worker has no window and no document, which is the
property that matters — an MV3 service worker has neither either. On Firefox:

  hasDOM:   false   hasWindow: false
  initWasm: ok  (9.0 ms cold)
  keygen:   1.0 ms
  account:  matches quantus-cli
  sign:     3.0 ms (4627 bytes)
  verify:   1.0 ms  ok
  ctx sep:  ok (rejected under spec-147 ctx)

9 ms to base64-decode 146 KB, inflate it to 234 KB and instantiate. That is the
number the MV3 lifetime question turns on — a worker killed between messages pays
it on every wake — and it settles the cold-start concern raised in quantus/wasm#1.
Those are ML-DSA-87 timings, the larger parameter set, so 65 is cheaper still.

The CSP is enforced, not merely declared: an earlier version of the page used an
inline script and Firefox blocked it, which is why main.js is a separate file.

The manual one is a real MV3 extension whose service worker imports the package
at module scope. Loading an unpacked extension needs an OS file dialog, so it
cannot be driven from here and is documented for a human to load. It covers
chrome.runtime messaging and the real kill-and-restart lifecycle rather than a
stand-in for it.

Refs quantus/wasm#1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-10 14:20:47 +03:00
rob thijssenandClaude Opus 5 b882f914e7 build(quantus-crypto): buildable, installable and tested as a package
The JS build now runs end to end and the built package has been consumed the way
quantus/common will consume it. Four things had to be worked out.

polkadot-dev-build-ts will not build this package. It returns early for any name
not starting with @polkadot/, in both buildJs and when collecting locals for
import rewriting. Renaming into someone else's scope to satisfy a string check
would be worse than not using the tool, and nothing is lost: this package needs
no deno variant, no rollup bundle, no cross-package import rewriting. A plain tsc
build lives in scripts/build-quantus-js.sh, which also keeps `yarn build:js`
byte-identical to upstream's behaviour.

binaryen 105 silently breaks the wasm. Upstream pins version_105 (2021), which
predates the externref tables wasm-bindgen 0.2.128 emits; wasm-opt "optimises"
the table into something that fails at instantiation with `WebAssembly.Table.
grow(): failed to grow table by 4`. The wasm is valid before wasm-opt and broken
after, every cargo test still passes, and it only surfaces when a consumer tries
to init. install-build-deps.sh now fetches binaryen 123 alongside, exactly as it
does a second wasm-bindgen.

The wasm-util dependency is imported deeply. Its package index re-exports
packageDetect, whose only job is a side effect registering with @polkadot/util —
a peer dependency we would inherit for nothing. base64 and fflate are pure
functions with no dependencies, so the deep paths are both lighter and honest.

ESM only, and the CJS scaffolding is removed. The consumers are ESM and the
wasm-bindgen glue is ESM-only, so a CJS variant would mean a second generated
glue or hand-written marshalling. Revisit if quantus/common's CJS build needs it.

Also: the pack step must run after tsc, which clears build/; the checked-in
bindings are refreshed by the build so they cannot drift; and both test suites
are wired into the repo's test script, which previously ran wasm-crypto's only.

The consumer test stages a real node_modules layout rather than testing in place,
because in this repo node_modules/@polkadot/wasm-util symlinks to the package
source, which carries no exports map — so a deep import resolves for a real
consumer and fails here for reasons that have nothing to do with our package.
Staging tests module resolution too, which is half of what can break in a
published package. It is also what caught the binaryen fault.

Post-wasm-opt: 234,292 raw / 109,649 zlib / 146,200 base64 — smaller than
upstream's entire wasm-crypto blob (335,277 / 168,782 / 225,044).

Refs quantus/wasm#1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-10 14:17:15 +03:00
rob thijssenandClaude Opus 5 02a3f004d7 chore: ignore the second bindgen download
install-build-deps.sh fetches wasm-bindgen 0.2.128 into bindgen-quantus/ for
packages/quantus-crypto, which the existing bindgen/ rule does not match.

Refs quantus/wasm#1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-10 13:58:35 +03:00
rob thijssenandClaude Opus 5 1f1f5729ab feat(quantus-crypto): JS surface, build scripts and CSP-safe sync init
Wraps the crate for JS consumers and adds the build that produces it.

Init deliberately avoids fetch and avoids @polkadot/wasm-bridge. The consumer is
an MV3 service worker under `script-src 'self' 'wasm-unsafe-eval'`, which can
compile WASM but not usefully fetch it, and which can be cold-started between any
two messages; callers like pair.sign() are synchronous and have no await to give.
So the WASM is zlib-compressed and base64'd into bytes.js at build time and
instantiated with wasm-bindgen's initSync. Bridge is not usable here regardless:
it implements the 0.2.79 JS-heap ABI and this crate builds with 0.2.128, which
uses externref tables.

build-quantus.sh is separate from build-wasm.sh rather than folded into it,
because that script drives the nightly-2022-06-24 + xargo build wasm-crypto
needs. install-build-deps.sh gains a second wasm-bindgen for the same reason —
the two ABIs cannot share a binary. No asm.js step: wasm2js over ML-DSA would be
enormous and slow, and every context we ship into permits wasm.

bytes.js is emitted in both module systems, with the CJS copy under a directory
carrying its own {"type":"commonjs"} — the package is "type": "module" and node
otherwise refuses to load an exports.-style file from it.

Proven end to end against the real build output: base64 -> inflate -> initSync
with no fetch, crystal_alice's account id matching the CLI through the JS path,
sig||pk matching the runtime's fixed-array size, and JsError surfacing as a JS
exception across the boundary.

Sizes are read from the crate rather than exposed as constants to copy. They are
consensus-critical and a drifted JS constant would re-frame every byte after the
signature while looking entirely healthy.

Refs quantus/wasm#1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-10 13:58:25 +03:00
rob thijssenandClaude Opus 5 6eb04f63ac feat(quantus-crypto): ML-DSA, Poseidon2 and HD derivation as a separate crate
Wraps the chain's own crypto crates for the browser: qp-rusty-crystals-dilithium
(ML-DSA-65 and ML-DSA-87), qp-poseidon-core for the account-id hash, and
qp-rusty-crystals-hdwallet for BIP44 derivation. Nothing is reimplemented — a
browser wallet that disagreed with the chain about a key or a signature would
emit well-formed output the chain rejects, with nothing on this side able to
tell.

A separate crate rather than more files in wasm-crypto, because the two cannot
share a Cargo graph. wasm-crypto builds with nightly-2022-06-24 against a
2019-era dependency set; the ML-DSA crates use inline `const {}` blocks that
need Rust >= 1.79. Bumping the older one would mean rewriting upstream's
sr25519/ed25519 build, which is the thing most worth leaving alone so rebases
stay boring. wasm-crypto is untouched here.

The scheme selector is the chain's own signature-enum variant index (0 for
ML-DSA-87, 1 for ML-DSA-65), so the number threaded through this API is the
byte that ends up on the wire and there is no mapping to get backwards. Key and
signature sizes are exported rather than left for JS to hardcode: they are
consensus-critical and a drifted constant would mis-frame every byte after the
signature while looking healthy.

Logic is split from the #[wasm_bindgen] wrappers because JsError cannot be
constructed off-wasm, which made every error path untestable by cargo test —
and the error paths are what most needs testing.

Verified against the `quantus` CLI 2.2.2 as an independent oracle, not against
our own output: the three dev-genesis account ids, and HD derivation at both
schemes' default paths from the public Substrate dev phrase. Context separation
is pinned too — a signature made under QUANTUS_EXTRINSIC must not verify under
the empty context, which is what makes the spec-148 boundary detectable rather
than a silent chain rejection.

Refs quantus/wasm#1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-10 13:54:25 +03:00
Tarik Gul 65286fb3ec Set headers to 2026 (#609)
Lock Threads / lock (push) Has been cancelled
2026-03-13 01:32:10 +02:00
github-actions[bot] f55a3e75a2 [CI Skip] release/stable 7.5.4
skip-checks: true
2025-12-09 10:03:06 +00:00
rajk93 6c8b0afd8d 7.5.4 (#606) 2025-12-09 15:27:27 +05:30
github-actions[bot] 9c6611087c [CI Skip] bump/beta 7.5.4-0-x
skip-checks: true
2025-12-09 09:23:51 +00:00
rajk93 dc34df3b54 chore: bump polkadot dependencies (#605) 2025-12-09 14:48:23 +05:30
github-actions[bot] 8bf7e88458 [CI Skip] release/stable 7.5.3
skip-checks: true
2025-11-24 08:24:07 +00:00
rajk93 e8f1fb3aed 7.5.3 (#604) 2025-11-24 13:50:24 +05:30
github-actions[bot] ba6155880e [CI Skip] bump/beta 7.5.3-0-x
skip-checks: true
2025-11-24 07:25:14 +00:00
rajk93 1e696a5520 chore: bump polkadot dependencies (#603) 2025-11-24 12:51:27 +05:30
github-actions[bot] b6c704eca8 [CI Skip] release/stable 7.5.2
skip-checks: true
2025-11-11 03:02:29 +00:00
rajk93 c7053fa112 7.5.2 (#602) 2025-11-11 08:24:34 +05:30
github-actions[bot] d76ab093a8 [CI Skip] bump/beta 7.5.2-1-x
skip-checks: true
2025-11-10 13:40:53 +00:00
rajk93 4a54a37ae1 chore: bump polkadot dependencies (#601) 2025-11-10 19:03:00 +05:30
github-actions[bot] 0eb433f3c2 [CI Skip] bump/beta 7.5.2-0-x
skip-checks: true
2025-11-10 13:09:40 +00:00
rajk93andFrancisco Valentim Castilho 278ecc7210 Fix/Revert asm build (#599)
* chore: revert asm build

* chore: revert info in build-wasm.sh

* chore: revert install-build-deps.sh

* chore: lock libc version

* chore: lock libc version in xargo

* chore: use --locked while building

* Fix: Switch `RUST_VER` to 1.63.0-nightly

* chore(CI): revert continue-on-error to false

* Revert "chore(CI): revert continue-on-error to false"

This reverts commit 425a93ea4d6c18e94ca04a0142818dd440442182.

---------

Co-authored-by: Francisco Valentim Castilho <franciscoannyon@gmail.com>
2025-11-10 18:31:44 +05:30
github-actions[bot] 03fe7dcb1d [CI Skip] release/stable 7.5.1
skip-checks: true
2025-08-25 14:05:16 +00:00
Valentin Fernandezandgithub-actions[bot] d99abde915 7.5.1 (#596)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-25 17:03:27 +03:00
github-actions[bot] 06502e8fe8 [CI Skip] bump/beta 7.4.2-4-x
skip-checks: true
2025-08-25 13:48:20 +00:00
Valentin Fernandezandgithub-actions[bot] f3004fdb31 bump deps (#595)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-25 16:46:30 +03:00
github-actions[bot] f5834e684f [CI Skip] bump/beta 7.4.2-3-x
skip-checks: true
2025-08-13 17:01:23 +00:00
Valentin Fernandezandgithub-actions[bot] d7a1060824 Remove ASM build (#594)
* Remove ASM build

* remove CI build blag

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-13 13:59:40 -03:00
c6f8748878 Tweak install-build-deps.sh script (#593)
* add additional flag

* downgrade bindgen

* Fix rust build

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: José Molina <jose@blockdeep.io>
2025-08-13 13:34:01 -03:00
Valentin Fernandezandgithub-actions[bot] 24a46c7262 Setup nightly as default (#592)
* run CI

* run CI

* setup nightly as default

* install  nightly-2024-11-22

* rustup show

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-13 17:01:17 +03:00
Valentin Fernandezandgithub-actions[bot] 78a282bda3 Readd lock file (#591)
* get rust versions

* modify build-wasm

* switch to nightly on build-wasm

* prevent script from using stable

* extra log

* new log

* run CI on current branch

* run CI on current branch

* Minor fixes

* Remove rust installation

* nightly on build.sh

* add log

* logs

* small change

* rust version is nightly

* auto-approve

* re add lock file

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-12 12:02:48 -03:00
Valentin Fernandezandgithub-actions[bot] 1713dccba3 Ci Fix (#590)
* get rust versions

* modify build-wasm

* switch to nightly on build-wasm

* prevent script from using stable

* extra log

* new log

* run CI on current branch

* run CI on current branch

* Minor fixes

* Remove rust installation

* nightly on build.sh

* add log

* logs

* small change

* rust version is nightly

* auto-approve

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-12 11:40:51 -03:00
Valentin Fernandezandgithub-actions[bot] 0a22251212 default to nightly build (#589)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-12 09:56:45 -03:00
Valentin Fernandezandgithub-actions[bot] 540d46717b default to installed rust version (#588)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-11 13:33:46 -03:00
Valentin Fernandezandgithub-actions[bot] e5112e690d fix rust version to 1.84 (#587)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-11 12:01:58 -03:00
Valentin Fernandezandgithub-actions[bot] 2f8c91fc15 rollback bindgen version change (#586)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-11 11:41:17 -03:00
rajk93 6d552f1ced Added validation checks in PBKDF2 and Scrypt hashing functions (#584)
* chore: added validation checks in PBKDF2 and Scrypt functions

* chore(eslint): ignore linting for mod.ts

* chore(ci): bump RUST_VER

* chore(ci): bump RUST_VER

* chore(CI): downgrade RUST_VER and update xargo build script

* chore(CI): revert last change and use RUST_VER as stable

* chore(CI): bump BINDGEN_VER

* chore(CI): bump BINDGEN_VER

* chore(CI): bump BINARYEN_VER

* chore(CI): downgrade BINARYEN_VER

* chore(CI): recomended fix for wasm-bindgen

* chore(test): point wasm to package

* Revert "chore(test): point wasm to package"

This reverts commit 66104540fdff0e760ca0a6a666d4e09d162c5769.

* chore(test): temporary test

* Revert "chore(test): temporary test"

This reverts commit af10b8d8b9bb597e02ea9c8d6cc3bf62676a6609.

* chore(test): added more logs to build-wasm.sh script

* Revert "chore(test): added more logs to build-wasm.sh script"

This reverts commit c5073005ae9daa84308296736a41636da8f3d21b.

* chore(test): added more logs to test all

* Revert "chore(test): added more logs to test all"

This reverts commit 9b0ac43c2c053ac408164bbf9b31b46d0844af1b.

* chore: add references for RFCs
2025-07-23 12:10:03 +05:30
rajk93 afa992b40b Revert CI improvements and wasm-bindgen version (#583)
* chore: revert wasm-bindgen

* chore: revert pull_request in github action workflow

* chore: revert CI improvements

* chore: revert CI improvements
2025-07-16 13:01:04 +05:30
Valentin Fernandez 401c6e5763 chore: Improve CI (#580)
* Prevent false possitive result

* Use stable cargo version

* Remove 2nd continue-on-error
2025-06-26 09:53:32 -03:00
rajk93andValentin Fernandez 76d051b16e chore: added check in ext_secp_recover for signature normalization (#579)
* chore: added check in ext_secp_recover for signature normalization

* update wasm-bindgen

* Add test for non-normalized signature

---------

Co-authored-by: Valentin Fernandez <tinchofernandez8@gmail.com>
2025-06-24 12:01:13 -03:00
github-actions[bot] 5b69d5aadc [CI Skip] bump/beta 7.4.2-2-x
skip-checks: true
2025-01-17 22:18:32 +00:00
Tarik Gul 780323538a Bump dev to 0.83.2 (#578) 2025-01-18 00:14:18 +02:00
github-actions[bot] 042d653852 [CI Skip] bump/beta 7.4.2-1-x
skip-checks: true
2025-01-02 19:57:35 +00:00
Tarik Gul da47390fa4 Set permissions on all scripts (#577)
* Set permissions on all scripts

* cleanup
2025-01-02 21:53:46 +02:00
Tarik Gul 488abd68bb Set execute for build script in CI (#576) 2025-01-02 21:40:37 +02:00
Tarik Gul d926ee63f4 Bump yarn to 4.6.0 (#575) 2025-01-02 21:26:31 +02:00
Tarik Gul 6777f8ef5e 2025 (#574)
* 2025

* include more files
2025-01-02 17:31:20 +02:00
github-actions[bot] 1001083009 [CI Skip] bump/beta 7.4.2-0-x
skip-checks: true
2024-10-22 14:24:12 +00:00
Tarik Gul 898b067081 Bump yarn to 4.5.1 (#573) 2024-10-22 17:20:13 +03:00
github-actions[bot] acc07b9345 [CI Skip] release/stable 7.4.1
skip-checks: true
2024-10-21 02:30:29 +00:00
Tarik Gul bc572b16d5 7.4.1 (#572)
* 7.4.1

* spacing
2024-10-21 05:27:10 +03:00
github-actions[bot] 91d2530ea8 [CI Skip] bump/beta 7.3.3-16-x
skip-checks: true
2024-10-21 00:59:52 +00:00
Tarik Gul e679298d8d Bump dev to 0.81.2 (#571) 2024-10-21 03:56:31 +03:00
Tarik Gul 949ad77dab Bump dev to 0.81.1 (#570) 2024-10-21 02:51:21 +03:00
github-actions[bot] 24ce35ae43 [CI Skip] bump/beta 7.3.3-15-x
skip-checks: true
2024-10-20 20:43:34 +00:00
Tarik Gul 9e22f34720 Set CI deno version to 1.42.x (#569) 2024-10-20 23:40:08 +03:00
github-actions[bot] 2b867b2e91 [CI Skip] bump/beta 7.3.3-14-x
skip-checks: true
2024-08-29 15:07:39 +00:00
Tarik Gul 230593ee17 Bump yarn to 4.4.1 (#568) 2024-08-29 18:02:48 +03:00
github-actions[bot] 60a3b8916c [CI Skip] bump/beta 7.3.3-13-x
skip-checks: true
2024-08-29 14:23:31 +00:00
Tarik Gul 1f20fdf377 Bump dev, and typescript (#567) 2024-08-29 17:18:39 +03:00
github-actions[bot] 64dcd40bee [CI Skip] bump/beta 7.3.3-12-x
skip-checks: true
2024-08-20 17:43:05 +00:00
Tarik Gul f3948b90f7 Upgrade deps (#566) 2024-08-20 20:37:19 +03:00
github-actions[bot] 83da578b60 [CI Skip] bump/beta 7.3.3-11-x
skip-checks: true
2024-05-14 00:43:00 +00:00
Tarik Gul 407ec8e1a1 Bump yarn to 4.2.2 (#565) 2024-05-14 03:38:02 +03:00
github-actions[bot] aa30920183 [CI Skip] bump/beta 7.3.3-10-x
skip-checks: true
2024-05-13 23:41:56 +00:00
Tarik Gul 35d56606bd Bump @polkadot/dev to 0.79.1 for topo sort (#564) 2024-05-14 02:37:40 +03:00
github-actions[bot] 4083abb1dd [CI Skip] bump/beta 7.3.3-9-x
skip-checks: true
2024-04-08 17:47:21 +00:00
Tarik Gul c6cb3a9226 Update ci checkout and setup_node v4 (#563) 2024-04-08 20:43:29 +03:00
github-actions[bot] 04d46701b1 [CI Skip] bump/beta 7.3.3-8-x
skip-checks: true
2024-03-26 01:14:57 +00:00
Tarik Gul 61f4c5eb7d Bump yarn to 4.1.1 (#562) 2024-03-26 03:11:57 +02:00
github-actions[bot] d0e99aebef [CI Skip] bump/beta 7.3.3-7-x
skip-checks: true
2024-02-17 13:49:09 +00:00
Tarik Gul dc752685d7 chore(yarn): update yarn to 4.1.0 (#560) 2024-02-17 15:43:24 +02:00
github-actions[bot] 9bf3c1be07 [CI Skip] bump/beta 7.3.3-6-x
skip-checks: true
2024-02-17 02:01:40 +00:00
Tarik Gul 32bd2172aa ci: add user (TarikGul) to auto-approve (#561) 2024-02-17 03:55:58 +02:00
github-actions[bot] 1f58821cd4 [CI Skip] bump/beta 7.3.3-5-x
skip-checks: true
2024-01-08 13:10:59 +00:00
Jaco 8352373fdd 2024 (#559) 2024-01-08 15:07:04 +02:00
github-actions[bot] 610fcee9cd [CI Skip] bump/beta 7.3.3-4-x
skip-checks: true
2023-12-19 15:09:13 +00:00
Jaco b086928d01 Add missing sideEffect declarations (#558) 2023-12-19 17:05:22 +02:00
github-actions[bot] 8b3bd58e79 [CI Skip] bump/beta 7.3.3-3-x
skip-checks: true
2023-12-19 08:46:15 +00:00
Jaco e6fd1e20a5 Bump dev w/ packageDetect adjustments (#557)
* Bump dev w/ packageDetect adjustments

* sideEffects
2023-12-19 10:42:52 +02:00
github-actions[bot] 22075f8262 [CI Skip] bump/beta 7.3.3-2-x
skip-checks: true
2023-12-18 12:05:07 +00:00
Jaco c8805a09dd Bump util (#556) 2023-12-18 14:01:44 +02:00
github-actions[bot] 758066b4ea [CI Skip] bump/beta 7.3.3-1-x
skip-checks: true
2023-12-18 08:29:42 +00:00
Jaco da7c71c32f Bump yarn berry, bump deps (#555) 2023-12-18 10:26:25 +02:00
github-actions[bot] cb38c6f1c5 [CI Skip] bump/beta 7.3.3-0-x
skip-checks: true
2023-12-12 05:34:17 +00:00
Jaco 39d5b3bcce Bump dev deps (w/ TS 5.3.3.) (#554) 2023-12-12 07:30:56 +02:00
150 changed files with 7832 additions and 4884 deletions
+1 -1
View File
@@ -11,6 +11,6 @@ jobs:
steps:
- uses: jacogr/action-approve@795afd1dd096a2071d7ec98740661af4e853b7da
with:
authors: jacogr
authors: jacogr, TarikGul, valentinfernandez1
labels: -auto
token: ${{ secrets.GH_PAT_BOT }}
+3 -3
View File
@@ -11,13 +11,13 @@ jobs:
env:
YARN_ENABLE_SCRIPTS: false
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- uses: denoland/setup-deno@v1
with:
deno-version: vx.x.x
deno-version: v1.42.x
- name: ${{ matrix.step }}
if: always()
continue-on-error: true
+7 -3
View File
@@ -18,15 +18,19 @@ jobs:
GH_RELEASE_GITHUB_API_TOKEN: ${{ secrets.GH_PAT_BOT }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_PAT_BOT }}
- uses: actions/setup-node@v3
ref: ${{ github.ref }}
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Set Execute Permissions
run: chmod +x ./scripts/*
- name: Run Install Build Deps
run: bash ./scripts/install-build-deps.sh
- name: build
run: |
yarn install --immutable
./scripts/install-build-deps.sh
yarn ${{ matrix.step }}
+3
View File
@@ -1,7 +1,10 @@
binaryen/
binaryen-quantus/
bindgen/
bindgen-quantus/
build/
build-*/
build-test/
bytes/
coverage/
node_modules/
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2023 @polkadot/wasm-crypto authors & contributors
// Copyright 2017-2026 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
module.exports = require('@polkadot/dev/config/prettier.cjs');
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-785
View File
File diff suppressed because one or more lines are too long
+934
View File
File diff suppressed because one or more lines are too long
+5 -8
View File
@@ -1,18 +1,15 @@
compressionLevel: mixed
enableGlobalCache: false
enableImmutableInstalls: false
enableProgressBars: false
logFilters:
# Discard any "cannot be found in cache" messages
- code: YN0013
level: discard
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
spec: "@yarnpkg/plugin-version"
yarnPath: .yarn/releases/yarn-3.2.0.cjs
yarnPath: .yarn/releases/yarn-4.6.0.cjs
+58
View File
@@ -1,5 +1,63 @@
# CHANGELOG
## 7.5.4 Dec 9, 2025
Changes:
- Bump polkadot-js dependencies ([#605](https://github.com/polkadot-js/wasm/pull/605))
## 7.5.3 Nov 24, 2025
Changes:
- Bump @polkadot dependencies ([#603](https://github.com/polkadot-js/wasm/pull/603))
## 7.5.2 Nov 10, 2025
Changes:
- Fix/Revert asm build ([#599](https://github.com/polkadot-js/wasm/pull/599))
- Bump @polkadot dependencies ([#601](https://github.com/polkadot-js/wasm/pull/601))
## 7.5.1 aug 25, 2025
Changes:
- Bump yarn to 4.5.1 ([#573](https://github.com/polkadot-js/wasm/pull/573))
- Update comments and labels from 2024 to 2025 ([#574](https://github.com/polkadot-js/wasm/pull/574))
- Bump yarn to 4.6.0 ([#575](https://github.com/polkadot-js/wasm/pull/575))
- Set execute for build script in CI ([#576](https://github.com/polkadot-js/wasm/pull/576))
- Set permissions on all scripts ([#577](https://github.com/polkadot-js/wasm/pull/577))
- Bump dev to 0.83.2 ([#578](https://github.com/polkadot-js/wasm/pull/578))
- chore: added check in ext_secp_recover for signature normalization ([#579](https://github.com/polkadot-js/wasm/pull/579))
- chore: Improve CI ([#580](https://github.com/polkadot-js/wasm/pull/580))
- Revert CI improvements and wasm-bindgen version ([#583](https://github.com/polkadot-js/wasm/pull/583))
- Added validation checks in PBKDF2 and Scrypt hashing functions ([#584](https://github.com/polkadot-js/wasm/pull/584))
- Rollback wasm-bindgen version change ([#586](https://github.com/polkadot-js/wasm/pull/586))
- Fix rust version to 1.84 ([#587](https://github.com/polkadot-js/wasm/pull/587))
- Default to installed rust version ([#588](https://github.com/polkadot-js/wasm/pull/588))
- Default to nightly rust version ([#589](https://github.com/polkadot-js/wasm/pull/589))
- Ci Fix ([#590](https://github.com/polkadot-js/wasm/pull/590))
- Read lock file ([#591](https://github.com/polkadot-js/wasm/pull/591))
- Setup nightly as default ([#592](https://github.com/polkadot-js/wasm/pull/592))
- Tweak install-build-deps.sh script ([#593](https://github.com/polkadot-js/wasm/pull/593))
- Remove ASM build ([#594](https://github.com/polkadot-js/wasm/pull/594))
- Bump @polkadot deps ([#595](https://github.com/polkadot-js/wasm/pull/595))
## 7.4.1 Oct 20, 2024
- Bump dev deps to 0.81.2
- Bump TS
- Ensure CJS is exported correctly
- Bump yarn
- Add missing sideEffect declarations
- Set Deno build in CI to 1.42.x
## 7.3.2 Dec 6, 2023
Changes:
+4 -1
View File
@@ -1,4 +1,7 @@
612 Jaco 7.3.2 (#553)
618 Jaco 2024 (#559)
19 Tarik Gul Bump dev to 0.83.2 (#578)
12 Valentin Fernandez 7.5.1 (#596)
10 rajk93 7.5.4 (#606)
1 Evgeny Fixed type (#121)
1 Shunfan Zhou Support sr25519 agreement (#209)
1 Steve Degosserie Expose Schnorrkel's VRF capabilities (#170)
+7 -2
View File
@@ -1,8 +1,13 @@
// Copyright 2017-2023 @polkadot/wasm-crypto authors & contributors
// Copyright 2017-2026 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import baseConfig from '@polkadot/dev/config/eslint';
export default [
...baseConfig
...baseConfig,
{
ignores: [
'mod.ts'
]
}
];
+14 -10
View File
@@ -6,7 +6,7 @@
},
"homepage": "https://github.com/polkadot-js/wasm#readme",
"license": "Apache-2.0",
"packageManager": "yarn@3.2.0",
"packageManager": "yarn@4.6.0",
"private": true,
"repository": {
"type": "git",
@@ -14,10 +14,10 @@
},
"sideEffects": false,
"type": "module",
"version": "7.3.2",
"version": "7.5.4",
"versions": {
"git": "7.3.2",
"npm": "7.3.2"
"git": "7.5.4",
"npm": "7.5.4"
},
"workspaces": [
"packages/*"
@@ -25,6 +25,8 @@
"scripts": {
"build": "yarn build:wasm",
"build:js": "./scripts/build-js.sh",
"build:quantus": "./scripts/build-quantus.sh",
"build:quantus:js": "./scripts/build-quantus-js.sh",
"build:release": "polkadot-ci-ghact-build",
"build:rollup": "polkadot-exec-rollup --config",
"build:wasm": "./scripts/build.sh",
@@ -34,8 +36,10 @@
"deno:check": "deno check --import-map=import_map.json mod.ts",
"lint": "polkadot-dev-run-lint",
"postinstall": "polkadot-dev-yarn-only",
"test": "yarn test:wasm-crypto:rust",
"test": "yarn test:wasm-crypto:rust && yarn test:quantus-crypto:rust",
"test:js": "yarn test:wasm-crypto:js",
"test:quantus-crypto:js": "./scripts/test-quantus-js.sh",
"test:quantus-crypto:rust": "cd packages/quantus-crypto && RUST_BACKTRACE=full cargo test --release",
"test:wasm-crypto:deno": "deno test --allow-read --import-map=import_map.json packages/wasm-crypto/test/deno.ts",
"test:wasm-crypto:js": "yarn test:wasm-crypto:js:jest && yarn test:wasm-crypto:js:node",
"test:wasm-crypto:js:jest": "polkadot-dev-run-test --env node --loader ./packages/wasm-crypto/test/loader-build.js",
@@ -43,12 +47,12 @@
"test:wasm-crypto:rust": "cd packages/wasm-crypto && RUST_BACKTRACE=full cargo test --release -- --test-threads=1 --nocapture"
},
"devDependencies": {
"@polkadot/dev": "^0.77.1",
"@polkadot/util": "^12.6.1",
"@types/node": "^20.9.1",
"fflate": "^0.8.1"
"@polkadot/dev": "^0.83.3",
"@polkadot/util": "^14.0.1",
"@types/node": "^20.16.1",
"fflate": "^0.8.2"
},
"resolutions": {
"typescript": "^5.2.2"
"typescript": "^5.5.4"
}
}
+955
View File
@@ -0,0 +1,955 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "anyhow"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "arrayvec"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bip39"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc"
dependencies = [
"bitcoin_hashes",
"zeroize",
]
[[package]]
name = "bitcoin_hashes"
version = "0.14.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2"
dependencies = [
"hex-conservative",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "either"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
name = "fixed-hash"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
dependencies = [
"static_assertions",
]
[[package]]
name = "futures-core"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-task"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"serde",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-conservative"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3fef046dca3ca91ee1408a8c1b80ab777e80a4d308d1bf4e7adb3fcb047e08"
dependencies = [
"arrayvec",
]
[[package]]
name = "hex-literal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
dependencies = [
"cfg-if",
"futures-util",
"wasm-bindgen",
]
[[package]]
name = "keccak-hash"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce2bd4c29270e724d3eaadf7bdc8700af4221fc0ed771b855eadcd1b98d52851"
dependencies = [
"primitive-types",
"tiny-keccak",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
dependencies = [
"critical-section",
"portable-atomic",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "plonky2_maybe_rayon"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1e554181dc95243b8d9948ae7bae5759c7fb2502fed28f671f95ef38079406"
[[package]]
name = "plonky2_util"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c32c137808ca984ab2458b612b7eb0462d853ee041a3136e83d54b96074c7610"
[[package]]
name = "portable-atomic"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "primitive-types"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373"
dependencies = [
"fixed-hash",
"uint",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "qp-plonky2"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd331d489a309f88e2d0e35a2b996932c7d92038b91ccc656a0a8e6b11b6977"
dependencies = [
"ahash",
"anyhow",
"critical-section",
"hashbrown",
"itertools",
"keccak-hash",
"log",
"num",
"once_cell",
"plonky2_maybe_rayon",
"plonky2_util",
"qp-plonky2-core",
"qp-plonky2-field",
"qp-plonky2-verifier",
"qp-poseidon-core",
"rand 0.10.1",
"serde",
"static_assertions",
"unroll",
]
[[package]]
name = "qp-plonky2-core"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b81a3a9fce99f7bd45b8578f8d9b6a33507d34c2eb2c47c969b464db1ad601d3"
dependencies = [
"ahash",
"anyhow",
"hashbrown",
"itertools",
"keccak-hash",
"log",
"num",
"plonky2_util",
"qp-plonky2-field",
"serde",
"static_assertions",
"unroll",
]
[[package]]
name = "qp-plonky2-field"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1630d418ddce9feba18d3364596711d07074851757301350de313eef0a31af4f"
dependencies = [
"anyhow",
"itertools",
"num",
"plonky2_util",
"rustc_version",
"serde",
"static_assertions",
"unroll",
]
[[package]]
name = "qp-plonky2-verifier"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "944da5dec21ee476d561f6c38caddf45e829f3cc5fccbc76f6ece03660378dbe"
dependencies = [
"ahash",
"anyhow",
"critical-section",
"hashbrown",
"itertools",
"keccak-hash",
"log",
"num",
"once_cell",
"plonky2_util",
"qp-plonky2-core",
"qp-plonky2-field",
"qp-poseidon-core",
"serde",
"static_assertions",
"unroll",
]
[[package]]
name = "qp-poseidon-core"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5872607e25ea4ee5fb37e64bf1462168e1a36a4e719cdc8a105533c708253918"
[[package]]
name = "qp-rusty-crystals-dilithium"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "789877c169226a35d2ea686bbd9d506becc693f7bb0ee91acc03f74491e80c0f"
dependencies = [
"zeroize",
]
[[package]]
name = "qp-rusty-crystals-hdwallet"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ec6c3db4055c217a503c45d0c101cf3c10d4fc1e562f0588aa55dda4f60a4d"
dependencies = [
"bip39",
"getrandom 0.2.17",
"hex",
"hex-literal",
"qp-poseidon-core",
"qp-rusty-crystals-dilithium",
"serde",
"serde_json",
"sha2",
"thiserror",
"unicode-normalization",
"zeroize",
]
[[package]]
name = "qp-wormhole-circuit"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55167daf965a3616b74184171148b94c64b3a48771d6433a1a43377a6e46c6e8"
dependencies = [
"anyhow",
"hex",
"qp-plonky2",
"qp-wormhole-inputs",
"qp-zk-circuits-common",
"zeroize",
]
[[package]]
name = "qp-wormhole-inputs"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c07863a2211a17b46319289c5ef22e5670a3fb8653386bb366c418193254793"
dependencies = [
"anyhow",
]
[[package]]
name = "qp-zk-circuits-common"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec45510701f160fdb730bdb622d6cdd0a62547e607c9b1ecb19558202797f81"
dependencies = [
"anyhow",
"qp-plonky2",
"qp-poseidon-core",
"qp-wormhole-inputs",
"rand 0.8.6",
"serde",
"serde_json",
]
[[package]]
name = "quantus_crypto"
version = "0.0.0"
dependencies = [
"qp-poseidon-core",
"qp-rusty-crystals-dilithium",
"qp-rusty-crystals-hdwallet",
"qp-wormhole-circuit",
"qp-zk-circuits-common",
"wasm-bindgen",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"libc",
"rand_chacha",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [
"getrandom 0.4.3",
"rand_core 0.10.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "rand_core"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "tinyvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "uint"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52"
dependencies = [
"byteorder",
"crunchy",
"hex",
"static_assertions",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-normalization"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
dependencies = [
"tinyvec",
]
[[package]]
name = "unroll"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 3.0.5",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "zerocopy"
version = "0.8.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+47
View File
@@ -0,0 +1,47 @@
# Quantus post-quantum crypto, compiled to WASM for the browser.
#
# Deliberately a separate crate from `wasm-crypto` rather than more files inside
# it: that package is built with `nightly-2022-06-24` (see scripts/rust-version.sh)
# against a 2019-era dependency set, and the ML-DSA crates use inline `const {}`
# blocks that need Rust >= 1.79. The two cannot share a Cargo graph, and bumping
# the older one would mean rewriting upstream's sr25519/ed25519 build — which is
# the thing we most want to leave alone so rebases stay boring. See quantus/wasm#1.
[package]
authors = ["Quantus Network Developers <hello@quantus.com>"]
description = "WASM bindings to the Quantus chain's post-quantum crypto crates."
edition = "2021"
license = "Apache-2.0"
name = "quantus_crypto"
publish = false
repository = "https://git.lair.cafe/quantus/wasm"
resolver = "2"
version = "0.0.0"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
# The crates the runtime itself uses. Versions match quantus-apps/quantus_sdk's
# rust bridge, which is the other non-Rust consumer of exactly this surface.
qp-poseidon-core = "3.1.0"
qp-rusty-crystals-dilithium = { version = "4.1.1", default-features = false, features = ["ml-dsa-65", "ml-dsa-87"] }
qp-rusty-crystals-hdwallet = { version = "4.1.1", default-features = false, features = ["ml-dsa-65", "ml-dsa-87"] }
wasm-bindgen = "0.2"
[dev-dependencies]
# The chain's own nullifier, for known-answer tests only. It pulls in plonky2,
# which has no place in the shipped WASM: the port in rs/hdwallet.rs has to
# agree with it, and these tests are how that is shown rather than asserted.
qp-wormhole-circuit = { version = "=4.3.0", default-features = false, features = ["std"] }
qp-zk-circuits-common = { version = "=4.3.0" }
[profile.release]
codegen-units = 1
debug = false
debug-assertions = false
incremental = false
lto = true
opt-level = "z"
panic = "abort"
rpath = false
+76
View File
@@ -0,0 +1,76 @@
# @quantus/crypto
Quantus post-quantum crypto for the browser: ML-DSA-65 and ML-DSA-87 signatures,
Poseidon2-over-Goldilocks account-id hashing, and hardened BIP44 key derivation.
Every function delegates to the crates the Quantus runtime itself uses —
`qp-rusty-crystals-dilithium`, `qp-poseidon-core`, `qp-rusty-crystals-hdwallet`
rather than reimplementing them. A browser wallet that disagreed with the chain
about a key or a signature would produce perfectly well-formed output that the
chain rejects, and nothing on this side could tell.
## Why a separate package from `@polkadot/wasm-crypto`
They cannot share a Cargo build. `wasm-crypto` is compiled with
`nightly-2022-06-24` against a 2019-era dependency set; the ML-DSA crates use
inline `const {}` blocks that require Rust >= 1.79. Modernising the older build
would mean rewriting upstream's sr25519/ed25519 crypto, which is the thing most
worth leaving untouched so rebases onto upstream stay boring.
What *is* shared is the packaging: the WASM is zlib-compressed and base64'd into
the JS at build time, so nothing is fetched at runtime. That matters because the
consumer is an MV3 service worker under `script-src 'self' 'wasm-unsafe-eval'`,
which can compile WASM but cannot usefully fetch it, and because callers like
`pair.sign()` are synchronous and have no `await` to give.
`@polkadot/wasm-bridge` is deliberately not used: its `Bridge` implements
wasm-bindgen 0.2.79's JS-heap ABI, while this crate builds with 0.2.128, which
uses externref tables. wasm-bindgen's own generated glue plus `initSync` is both
smaller and correct.
The only runtime dependency is `fflate`, for zlib inflate. Base64 decoding is
fifteen lines here rather than a dependency. Both were originally taken from
`@polkadot/wasm-util`, which turned out to cost more than it saved: its index
re-exports `packageDetect`, dragging in a `@polkadot/util` peer dependency for a
side effect we do not want, and being a workspace package it resolved through its
*own* repo's node_modules when this package was consumed by symlink from another
checkout — which is exactly how `quantus/common` consumes it during development.
## Scheme selector
`Scheme.MlDsa87 = 0`, `Scheme.MlDsa65 = 1` — these are the chain's own
`DilithiumSignatureScheme` variant indices, so the number threaded through this
API is the byte that ends up on the wire. New accounts use ML-DSA-65; ML-DSA-87
is legacy and must be supported but never chosen.
## Signing context
ML-DSA hashes a context into the signature. Quantus extrinsics on spec >= 148 are
verified under `QUANTUS_EXTRINSIC`, earlier specs under the empty context. A
signature made under the wrong one is cryptographically valid, rejected by the
chain, and indistinguishable locally — so use `contextForSpec(specVersion)`
rather than picking one by hand. Nothing here guesses on your behalf.
## Sizes come from the crate
`sizes(scheme)` returns the public/secret/signature lengths rather than exposing
constants to copy. They are consensus-critical — the runtime decodes a fixed-size
array with no compact length prefix — and a JS constant that drifted would
re-frame every byte after the signature while looking entirely healthy.
## Building
```sh
yarn install-build-deps # downloads wasm-bindgen 0.2.128 and binaryen
./scripts/build-quantus.sh
```
The Rust toolchain is pinned in `rust-toolchain.toml` to the same channel the
chain builds its runtime with.
## Tests
`cargo test` runs conformance tests whose expected values come from the `quantus`
CLI, not from this crate — the dev-genesis account ids, HD derivation at both
schemes' default paths, and context separation. A test that pinned our own output
would keep passing through exactly the drift they exist to catch.
+24
View File
@@ -0,0 +1,24 @@
{
"author": "Quantus Network Developers <hello@quantus.com>",
"bugs": "https://git.lair.cafe/quantus/wasm/issues",
"description": "Quantus post-quantum crypto (ML-DSA, Poseidon2, HD derivation) for the browser",
"engines": {
"node": ">=18"
},
"homepage": "https://git.lair.cafe/quantus/wasm/src/branch/main/packages/quantus-crypto#readme",
"license": "Apache-2.0",
"name": "@quantus/crypto",
"repository": {
"directory": "packages/quantus-crypto",
"type": "git",
"url": "https://git.lair.cafe/quantus/wasm.git"
},
"sideEffects": false,
"type": "module",
"version": "0.3.0",
"main": "index.js",
"dependencies": {
"fflate": "^0.8.2",
"tslib": "^2.7.0"
}
}
@@ -0,0 +1,8 @@
# Matches the chain's toolchain (chain/rust-toolchain), so this crate is built by
# the same compiler that builds the runtime it has to agree with. Upstream's
# `wasm-crypto` keeps its own nightly-2022-06-24 pin; the two builds are separate
# on purpose. See quantus/wasm#1.
[toolchain]
channel = "1.93.0"
targets = ["wasm32-unknown-unknown"]
profile = "minimal"
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// An array indexer rather than a Map: the input is ASCII by construction, so it
// cannot overflow, and array access is measurably faster on the hot loop.
const MAP = new Array<number>(256);
for (let i = 0; i < CHARS.length; i++) {
MAP[CHARS.charCodeAt(i)] = i;
}
/**
* Decode base64 into a caller-supplied buffer.
*
* Deliberately not `atob` or `Buffer.from`: the first is browser-only, the second
* node-only, and this runs in an MV3 service worker, a Worker, node tests and a
* bundled extension page. The output length is known at build time, so the
* caller provides the buffer and there is no growth or reallocation.
*
* This is a reimplementation of `@polkadot/wasm-util`'s base64Decode, which was
* the dependency it replaced. That package's index re-exports `packageDetect`,
* dragging in a `@polkadot/util` peer dependency for a side effect we do not
* want, and being a workspace package it resolved through its own repo's
* node_modules when consumed by symlink from another checkout. Fifteen lines is
* cheaper than either problem.
*/
export function base64Decode (data: string, out: Uint8Array): Uint8Array {
let byte = 0;
let bits = 0;
let pos = 0;
for (let i = 0; i < data.length && pos < out.length; i++) {
const value = MAP[data.charCodeAt(i)];
if (value === undefined) {
continue;
}
byte = (byte << 6) | value;
bits += 6;
if (bits >= 8) {
bits -= 8;
out[pos++] = (byte >>> bits) & 0xff;
}
}
return out;
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
export declare const lenIn: number;
export declare const lenOut: number;
export declare const bytes: string;
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Generated as part of the build, do not edit
export const lenIn = 0;
export const lenOut = 0;
export const bytes = '';
+210
View File
@@ -0,0 +1,210 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ext_mldsa_derive, ext_mldsa_from_seed, ext_mldsa_is_scheme, ext_mldsa_sign, ext_mldsa_sizes, ext_mldsa_verify, ext_poseidon_hash, ext_wormhole_addresses, ext_wormhole_nullifiers } from './generated/quantus_crypto.js';
import { initWasm } from './init.js';
import { Scheme } from './scheme.js';
export interface Keypair {
publicKey: Uint8Array;
secretKey: Uint8Array;
}
export interface Sizes {
/** Public key length: 1952 for ML-DSA-65, 2592 for ML-DSA-87. */
publicKey: number;
/** Secret key length: 4032 / 4896. */
secretKey: number;
/** Signature length: 3309 / 4627. */
signature: number;
/** `signature ‖ publicKey`, the runtime's wire form: 5261 / 7219. */
signatureWithPublicKey: number;
}
function ready (): void {
const error = initWasm();
if (error) {
throw new Error(`@quantus/crypto: WASM unavailable: ${error}`);
}
}
/**
* Key and signature sizes for a scheme, read from the crate rather than
* hardcoded here.
*
* These are consensus-critical — the runtime decodes a fixed-size array off the
* wire — so a constant that drifted from the crate would re-frame every byte
* after the signature while looking entirely healthy. Ask, don't assume.
*/
export function sizes (scheme: Scheme): Sizes {
ready();
const raw = new DataView(ext_mldsa_sizes(scheme).buffer);
return {
publicKey: raw.getUint32(0, true),
secretKey: raw.getUint32(4, true),
signature: raw.getUint32(8, true),
signatureWithPublicKey: raw.getUint32(12, true)
};
}
/** Whether this build supports `scheme`. */
export function isScheme (scheme: number): scheme is Scheme {
ready();
return ext_mldsa_is_scheme(scheme);
}
/**
* The account id for a public key — Poseidon2 over Goldilocks, 32 bytes out.
*
* This is the step that has no Substrate equivalent. There, an `AccountId32`
* *is* the public key; here it is a one-way hash of it, which is why a Quantus
* signature has to carry its public key inside itself and why nothing can
* recover a key from an address.
*/
export function accountFromPublicKey (publicKey: Uint8Array): Uint8Array {
ready();
return ext_poseidon_hash(publicKey);
}
/** A keypair from 32 bytes of entropy — FIPS 204 `ML-DSA.KeyGen_internal`. */
export function keypairFromSeed (seed: Uint8Array, scheme: Scheme): Keypair {
ready();
return split(ext_mldsa_from_seed(seed, scheme), scheme);
}
/**
* A keypair from a BIP39 mnemonic at a hardened Quantus derivation path.
*
* `path` must be hardened at every level — lattice keys have no public
* derivability, so there is no soft-junction equivalent and one is rejected
* rather than reinterpreted.
*/
export function keypairFromMnemonic (mnemonic: string, password: string, path: string, scheme: Scheme): Keypair {
ready();
return split(ext_mldsa_derive(mnemonic, password, path, scheme), scheme);
}
/** Which wormhole branch: where deposits arrive, or where a send returns its change. */
export enum WormholeBranch {
Receive = 0,
Change = 1
}
/**
* Consecutive wormhole addresses for one account and branch, as 32-byte account
* ids.
*
* Paths are `m/44'/189189189'/<account>'/<branch>'/<index>'`, the mobile
* wallet's. A wormhole address has no key: it is a double Poseidon hash of a
* secret the path yields, and funds leave it only through a ZK proof of that
* secret. Only the addresses cross into JavaScript; the secrets are derived and
* wiped inside WASM.
*
* The recovery phrase is stretched once per call, so ask for a window of
* addresses at once rather than looping over single ones. At most 1000 per call.
*/
export function wormholeAddresses (mnemonic: string, password: string, account: number, branch: WormholeBranch, start: number, count: number): Uint8Array[] {
ready();
const flat = ext_wormhole_addresses(mnemonic, password, account, branch, start, count);
const out: Uint8Array[] = [];
for (let i = 0; i < count; i++) {
out.push(flat.slice(i * 32, (i + 1) * 32));
}
return out;
}
/**
* Nullifiers for a run of wormhole addresses, one per deposit transfer count.
*
* A deposit to a wormhole address is spent when its nullifier is in
* `Wormhole::UsedNullifiers`. For each address `start..start + addresses` on
* `branch` of `account`, this returns the nullifiers for transfer counts
* `first..first + count`: `result[a][c]` belongs to address `start + a` and
* transfer count `first + c`.
*
* Check them locally, against a copy of the whole spent set. Never look one up
* by key or send it to a service: exits publish their nullifiers, so whoever
* sees yours can name your exits. At most 100,000 per call; the recovery phrase
* is stretched once per call.
*/
export function wormholeNullifiers (mnemonic: string, password: string, account: number, branch: WormholeBranch, start: number, addresses: number, first: number, count: number): Uint8Array[][] {
ready();
const flat = ext_wormhole_nullifiers(mnemonic, password, account, branch, start, addresses, BigInt(first), count);
const out: Uint8Array[][] = [];
for (let a = 0; a < addresses; a++) {
const row: Uint8Array[] = [];
for (let c = 0; c < count; c++) {
const at = (a * count + c) * 32;
row.push(flat.slice(at, at + 32));
}
out.push(row);
}
return out;
}
/**
* Sign under a FIPS 204 context.
*
* `context` is not optional in spirit: extrinsics on spec >= 148 verify under
* `QUANTUS_EXTRINSIC` and earlier ones under the empty context, and the wrong
* choice yields a valid signature that the chain rejects with nothing locally
* able to tell. Use `contextForSpec` rather than picking one by hand.
*
* Returns the bare signature. The runtime's wire form is `signature ‖ publicKey`
* — see {@link signatureWithPublicKey} — but only the caller knows which it
* wants.
*/
export function sign (message: Uint8Array, { publicKey, secretKey }: Keypair, context: Uint8Array, scheme: Scheme): Uint8Array {
ready();
return ext_mldsa_sign(secretKey, publicKey, message, context, scheme);
}
/** Verify a bare signature under a context. */
export function verify (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array, context: Uint8Array, scheme: Scheme): boolean {
ready();
return ext_mldsa_verify(publicKey, message, signature, context, scheme);
}
/**
* `signature ‖ publicKey` — what a signed extrinsic actually carries.
*
* The runtime encodes this as a fixed-size array with **no compact length
* prefix**, preceded by the scheme's enum variant byte. Getting that framing
* wrong re-frames every byte after it into something that still decodes.
*/
export function signatureWithPublicKey (signature: Uint8Array, publicKey: Uint8Array): Uint8Array {
const out = new Uint8Array(signature.length + publicKey.length);
out.set(signature);
out.set(publicKey, signature.length);
return out;
}
/** The crate returns `secretKey ‖ publicKey`, matching `ext_ed_from_seed`. */
function split (pair: Uint8Array, scheme: Scheme): Keypair {
const { secretKey } = sizes(scheme);
return {
publicKey: pair.subarray(secretKey),
secretKey: pair.subarray(0, secretKey)
};
}
@@ -0,0 +1,226 @@
/* tslint:disable */
/* eslint-disable */
/**
* Derive a keypair from a BIP39 mnemonic at a hardened derivation path.
*
* Lattice keys have no public derivability, so there is no soft-junction
* equivalent and the crate rejects any unhardened path outright. The Quantus
* convention is:
*
* ```text
* m/44'/189189'/<account>'/0'/<0 for ML-DSA-87 | 1 for ML-DSA-65>'
* ```
*
* with the account index at the third level and the *scheme* carried in the
* trailing index. That is unusual, and it is what `quantus-cli` and the mobile
* wallet already use — deriving anything else produces addresses no other
* Quantus tool can find.
*
* The seeding matters as much as the path. This goes mnemonic → 64-byte BIP39
* seed → HMAC-SHA512 chain keyed with the literal string `"Dilithium seed"`.
* Substrate's own `mnemonicToMiniSecret` is a *different* derivation and is the
* default reach in the polkadot-js codebase; using it here would yield a
* well-formed key for an account nobody owns.
*
* * mnemonic: BIP39 phrase, 12/15/18/21/24 words
* * password: BIP39 passphrase; empty string for none
* * path: hardened derivation path, e.g. `m/44'/189189'/0'/0'/1'`
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
*
* * returned vector is the secret key followed by the public key, as
* `ext_mldsa_from_seed` returns.
*/
export function ext_mldsa_derive(mnemonic: string, password: string, path: string, scheme: number): Uint8Array;
/**
* Generate a keypair from 32 bytes of entropy.
*
* This is FIPS 204 `ML-DSA.KeyGen_internal` with no Quantus-specific step: the
* crate expands the seed as `SHAKE256(seed ‖ k ‖ )`, so the parameter set is
* absorbed into the expansion and the same 32 bytes yield independent keys per
* scheme. That is why the dev accounts (`[0u8; 32]`, `[1u8; 32]`, `[2u8; 32]`)
* and HD-derived accounts can share this one entry point.
*
* * seed: UIntArray with 32 elements
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
*
* * returned vector is the secret key followed by the public key, matching the
* ordering `ext_ed_from_seed` uses. Split it at the secret length from
* `ext_mldsa_sizes`.
*/
export function ext_mldsa_from_seed(seed: Uint8Array, scheme: number): Uint8Array;
/**
* Whether `scheme` names a parameter set this build supports.
*
* `dispatch!` falls back to ML-DSA-87 for anything unrecognised, which is the
* right default but a poor way to discover a typo. Callers that accept a scheme
* from storage or from a user should check here first.
*/
export function ext_mldsa_is_scheme(scheme: number): boolean;
/**
* Sign a message under a FIPS 204 context.
*
* Signing is deterministic — no hedging randomness — because that is what the
* runtime does (`hedge: None`), and a wallet that hedged would produce a
* different signature each time for the same input, which makes the
* byte-for-byte agreement tests in quantus/wasm#2 impossible to write.
*
* `ctx` is domain separation and it is **not** optional in practice: extrinsics
* on spec >= 148 are verified under `QUANTUS_EXTRINSIC`, earlier specs under the
* empty context, and a signature made under the wrong one is valid, rejected by
* the chain, and indistinguishable locally. The caller chooses; this function
* does not guess.
*
* * secret: UIntArray, secret-key length for the scheme
* * public: UIntArray, public-key length for the scheme
* * message: arbitrary length UIntArray
* * ctx: UIntArray, at most 255 elements; empty for no context
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
*
* * returned vector is the signature alone. The runtime's wire format is
* `signature ‖ public`; concatenating is the caller's job because only the
* caller knows whether it wants the wire form or the bare signature.
*/
export function ext_mldsa_sign(secret: Uint8Array, _public: Uint8Array, message: Uint8Array, ctx: Uint8Array, scheme: number): Uint8Array;
/**
* Key and signature sizes for a parameter set, as
* `[public, secret, signature, signature_with_public]`.
*
* Exported so that nothing on the JS side has to hardcode 1952/4032/3309/5261 or
* 2592/4896/4627/7219. Those numbers are consensus-critical — the runtime reads a
* fixed-size array off the wire — and a JS constant that drifted from the crate
* would mis-frame every byte after the signature while looking entirely healthy.
* Ask the crate instead.
*
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
*
* * returned vector is four u32 lengths, little-endian, 16 bytes total.
*/
export function ext_mldsa_sizes(scheme: number): Uint8Array;
/**
* Verify a signature against a message and public key under a context.
*
* * public: UIntArray, public-key length for the scheme
* * message: arbitrary length UIntArray
* * signature: UIntArray, signature length for the scheme
* * ctx: UIntArray, at most 255 elements; empty for no context
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
*/
export function ext_mldsa_verify(_public: Uint8Array, message: Uint8Array, signature: Uint8Array, ctx: Uint8Array, scheme: number): boolean;
/**
* Poseidon2-over-Goldilocks hash of arbitrary bytes.
*
* This is the account-id derivation. On Substrate an `AccountId32` *is* the
* public key; on Quantus it is `hash_bytes(public_key)`, which is why a Quantus
* signature has to carry its public key along — the address cannot give it back.
*
* `qp_poseidon_core::hash_bytes` is `IdentifyAccount for DilithiumSigner` in the
* runtime, so this is the same function the chain uses to decide who signed
* something, reached through the same crate rather than a port of it.
*
* * data: arbitrary length UIntArray
*
* * returned vector is 32 bytes.
*/
export function ext_poseidon_hash(data: Uint8Array): Uint8Array;
/**
* Derive consecutive wormhole addresses for one account and branch.
*
* A wormhole address is not a key. The path yields a 32-byte **secret**, and
* the address is `poseidon(poseidon(salt ‖ secret))`; funds leave it only
* through a ZK proof of knowing that secret. So this returns the addresses and
* nothing else. The secrets and the intermediate `first_hash` stay inside this
* call and are wiped on drop by the crate's sensitive types.
*
* Paths are `m/44'/189189189'/<account>'/<change>'/<index>'`, as the mobile
* wallet derives them: change `0` is the receive branch, `1` the change branch.
*
* The BIP39 seed is stretched once for the whole batch. Stretching per address
* is PBKDF2 with 2048 rounds each time, and a gap-limit window is dozens of
* addresses.
*
* * mnemonic: BIP39 phrase
* * password: BIP39 passphrase; empty string for none
* * account, change, start, count: which addresses; count <= WORMHOLE_MAX_BATCH
*
* * returned vector is `count` 32-byte account ids, concatenated
*/
export function ext_wormhole_addresses(mnemonic: string, password: string, account: number, change: number, start: number, count: number): Uint8Array;
/**
* Nullifiers for a run of wormhole addresses' deposits, by transfer count.
*
* A deposit to a wormhole address is spent when its nullifier is in
* `Wormhole::UsedNullifiers`. The nullifier is
*
* ```text
* poseidon2(poseidon2(salt("~nullif~") ‖ secret ‖ transfer_count))
* ```
*
* where `transfer_count` is the address's counter when the deposit landed. It
* needs the address's secret, which is why this takes the recovery phrase and
* why a nullifier should never be sent anywhere to be checked: exits publish
* nullifiers, so whoever sees yours can name your exits.
*
* For addresses `m/44'/189189189'/<account>'/<change>'/<index>'` with index in
* `start..start + addresses`, and transfer counts `first..first + count` for
* each. The BIP39 seed is stretched once for the whole run.
*
* Ported onto `qp-poseidon-core` rather than calling `qp-wormhole-circuit`,
* which would bring plonky2 into the WASM. The port is pinned to the circuit
* crate's own `Nullifier::from_preimage` by the tests.
*
* * returned vector is `addresses * count` 32-byte nullifiers: address by
* address, and by transfer count within each
*/
export function ext_wormhole_nullifiers(mnemonic: string, password: string, account: number, change: number, start: number, addresses: number, first: bigint, count: number): Uint8Array;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly ext_mldsa_derive: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
readonly ext_mldsa_from_seed: (a: number, b: number, c: number) => [number, number, number, number];
readonly ext_mldsa_is_scheme: (a: number) => number;
readonly ext_mldsa_sign: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => [number, number, number, number];
readonly ext_mldsa_sizes: (a: number) => [number, number];
readonly ext_mldsa_verify: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => number;
readonly ext_poseidon_hash: (a: number, b: number) => [number, number];
readonly ext_wormhole_addresses: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => [number, number, number, number];
readonly ext_wormhole_nullifiers: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: bigint, j: number) => [number, number, number, number];
readonly __wbindgen_externrefs: WebAssembly.Table;
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __externref_table_dealloc: (a: number) => void;
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
readonly __wbindgen_start: () => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
*
* @returns {InitOutput}
*/
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
@@ -0,0 +1,503 @@
/* @ts-self-types="./quantus_crypto.d.ts" */
/**
* Derive a keypair from a BIP39 mnemonic at a hardened derivation path.
*
* Lattice keys have no public derivability, so there is no soft-junction
* equivalent and the crate rejects any unhardened path outright. The Quantus
* convention is:
*
* ```text
* m/44'/189189'/<account>'/0'/<0 for ML-DSA-87 | 1 for ML-DSA-65>'
* ```
*
* with the account index at the third level and the *scheme* carried in the
* trailing index. That is unusual, and it is what `quantus-cli` and the mobile
* wallet already use — deriving anything else produces addresses no other
* Quantus tool can find.
*
* The seeding matters as much as the path. This goes mnemonic → 64-byte BIP39
* seed → HMAC-SHA512 chain keyed with the literal string `"Dilithium seed"`.
* Substrate's own `mnemonicToMiniSecret` is a *different* derivation and is the
* default reach in the polkadot-js codebase; using it here would yield a
* well-formed key for an account nobody owns.
*
* * mnemonic: BIP39 phrase, 12/15/18/21/24 words
* * password: BIP39 passphrase; empty string for none
* * path: hardened derivation path, e.g. `m/44'/189189'/0'/0'/1'`
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
*
* * returned vector is the secret key followed by the public key, as
* `ext_mldsa_from_seed` returns.
* @param {string} mnemonic
* @param {string} password
* @param {string} path
* @param {number} scheme
* @returns {Uint8Array}
*/
export function ext_mldsa_derive(mnemonic, password, path, scheme) {
const ptr0 = passStringToWasm0(mnemonic, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passStringToWasm0(path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len2 = WASM_VECTOR_LEN;
const ret = wasm.ext_mldsa_derive(ptr0, len0, ptr1, len1, ptr2, len2, scheme);
if (ret[3]) {
throw takeFromExternrefTable0(ret[2]);
}
var v4 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v4;
}
/**
* Generate a keypair from 32 bytes of entropy.
*
* This is FIPS 204 `ML-DSA.KeyGen_internal` with no Quantus-specific step: the
* crate expands the seed as `SHAKE256(seed ‖ k ‖ )`, so the parameter set is
* absorbed into the expansion and the same 32 bytes yield independent keys per
* scheme. That is why the dev accounts (`[0u8; 32]`, `[1u8; 32]`, `[2u8; 32]`)
* and HD-derived accounts can share this one entry point.
*
* * seed: UIntArray with 32 elements
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
*
* * returned vector is the secret key followed by the public key, matching the
* ordering `ext_ed_from_seed` uses. Split it at the secret length from
* `ext_mldsa_sizes`.
* @param {Uint8Array} seed
* @param {number} scheme
* @returns {Uint8Array}
*/
export function ext_mldsa_from_seed(seed, scheme) {
const ptr0 = passArray8ToWasm0(seed, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.ext_mldsa_from_seed(ptr0, len0, scheme);
if (ret[3]) {
throw takeFromExternrefTable0(ret[2]);
}
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v2;
}
/**
* Whether `scheme` names a parameter set this build supports.
*
* `dispatch!` falls back to ML-DSA-87 for anything unrecognised, which is the
* right default but a poor way to discover a typo. Callers that accept a scheme
* from storage or from a user should check here first.
* @param {number} scheme
* @returns {boolean}
*/
export function ext_mldsa_is_scheme(scheme) {
const ret = wasm.ext_mldsa_is_scheme(scheme);
return ret !== 0;
}
/**
* Sign a message under a FIPS 204 context.
*
* Signing is deterministic — no hedging randomness — because that is what the
* runtime does (`hedge: None`), and a wallet that hedged would produce a
* different signature each time for the same input, which makes the
* byte-for-byte agreement tests in quantus/wasm#2 impossible to write.
*
* `ctx` is domain separation and it is **not** optional in practice: extrinsics
* on spec >= 148 are verified under `QUANTUS_EXTRINSIC`, earlier specs under the
* empty context, and a signature made under the wrong one is valid, rejected by
* the chain, and indistinguishable locally. The caller chooses; this function
* does not guess.
*
* * secret: UIntArray, secret-key length for the scheme
* * public: UIntArray, public-key length for the scheme
* * message: arbitrary length UIntArray
* * ctx: UIntArray, at most 255 elements; empty for no context
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
*
* * returned vector is the signature alone. The runtime's wire format is
* `signature ‖ public`; concatenating is the caller's job because only the
* caller knows whether it wants the wire form or the bare signature.
* @param {Uint8Array} secret
* @param {Uint8Array} _public
* @param {Uint8Array} message
* @param {Uint8Array} ctx
* @param {number} scheme
* @returns {Uint8Array}
*/
export function ext_mldsa_sign(secret, _public, message, ctx, scheme) {
const ptr0 = passArray8ToWasm0(secret, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passArray8ToWasm0(_public, wasm.__wbindgen_malloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passArray8ToWasm0(message, wasm.__wbindgen_malloc);
const len2 = WASM_VECTOR_LEN;
const ptr3 = passArray8ToWasm0(ctx, wasm.__wbindgen_malloc);
const len3 = WASM_VECTOR_LEN;
const ret = wasm.ext_mldsa_sign(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, scheme);
if (ret[3]) {
throw takeFromExternrefTable0(ret[2]);
}
var v5 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v5;
}
/**
* Key and signature sizes for a parameter set, as
* `[public, secret, signature, signature_with_public]`.
*
* Exported so that nothing on the JS side has to hardcode 1952/4032/3309/5261 or
* 2592/4896/4627/7219. Those numbers are consensus-critical — the runtime reads a
* fixed-size array off the wire — and a JS constant that drifted from the crate
* would mis-frame every byte after the signature while looking entirely healthy.
* Ask the crate instead.
*
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
*
* * returned vector is four u32 lengths, little-endian, 16 bytes total.
* @param {number} scheme
* @returns {Uint8Array}
*/
export function ext_mldsa_sizes(scheme) {
const ret = wasm.ext_mldsa_sizes(scheme);
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v1;
}
/**
* Verify a signature against a message and public key under a context.
*
* * public: UIntArray, public-key length for the scheme
* * message: arbitrary length UIntArray
* * signature: UIntArray, signature length for the scheme
* * ctx: UIntArray, at most 255 elements; empty for no context
* * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
* @param {Uint8Array} _public
* @param {Uint8Array} message
* @param {Uint8Array} signature
* @param {Uint8Array} ctx
* @param {number} scheme
* @returns {boolean}
*/
export function ext_mldsa_verify(_public, message, signature, ctx, scheme) {
const ptr0 = passArray8ToWasm0(_public, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passArray8ToWasm0(message, wasm.__wbindgen_malloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passArray8ToWasm0(signature, wasm.__wbindgen_malloc);
const len2 = WASM_VECTOR_LEN;
const ptr3 = passArray8ToWasm0(ctx, wasm.__wbindgen_malloc);
const len3 = WASM_VECTOR_LEN;
const ret = wasm.ext_mldsa_verify(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, scheme);
return ret !== 0;
}
/**
* Poseidon2-over-Goldilocks hash of arbitrary bytes.
*
* This is the account-id derivation. On Substrate an `AccountId32` *is* the
* public key; on Quantus it is `hash_bytes(public_key)`, which is why a Quantus
* signature has to carry its public key along — the address cannot give it back.
*
* `qp_poseidon_core::hash_bytes` is `IdentifyAccount for DilithiumSigner` in the
* runtime, so this is the same function the chain uses to decide who signed
* something, reached through the same crate rather than a port of it.
*
* * data: arbitrary length UIntArray
*
* * returned vector is 32 bytes.
* @param {Uint8Array} data
* @returns {Uint8Array}
*/
export function ext_poseidon_hash(data) {
const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.ext_poseidon_hash(ptr0, len0);
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v2;
}
/**
* Derive consecutive wormhole addresses for one account and branch.
*
* A wormhole address is not a key. The path yields a 32-byte **secret**, and
* the address is `poseidon(poseidon(salt ‖ secret))`; funds leave it only
* through a ZK proof of knowing that secret. So this returns the addresses and
* nothing else. The secrets and the intermediate `first_hash` stay inside this
* call and are wiped on drop by the crate's sensitive types.
*
* Paths are `m/44'/189189189'/<account>'/<change>'/<index>'`, as the mobile
* wallet derives them: change `0` is the receive branch, `1` the change branch.
*
* The BIP39 seed is stretched once for the whole batch. Stretching per address
* is PBKDF2 with 2048 rounds each time, and a gap-limit window is dozens of
* addresses.
*
* * mnemonic: BIP39 phrase
* * password: BIP39 passphrase; empty string for none
* * account, change, start, count: which addresses; count <= WORMHOLE_MAX_BATCH
*
* * returned vector is `count` 32-byte account ids, concatenated
* @param {string} mnemonic
* @param {string} password
* @param {number} account
* @param {number} change
* @param {number} start
* @param {number} count
* @returns {Uint8Array}
*/
export function ext_wormhole_addresses(mnemonic, password, account, change, start, count) {
const ptr0 = passStringToWasm0(mnemonic, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.ext_wormhole_addresses(ptr0, len0, ptr1, len1, account, change, start, count);
if (ret[3]) {
throw takeFromExternrefTable0(ret[2]);
}
var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v3;
}
/**
* Nullifiers for a run of wormhole addresses' deposits, by transfer count.
*
* A deposit to a wormhole address is spent when its nullifier is in
* `Wormhole::UsedNullifiers`. The nullifier is
*
* ```text
* poseidon2(poseidon2(salt("~nullif~") ‖ secret ‖ transfer_count))
* ```
*
* where `transfer_count` is the address's counter when the deposit landed. It
* needs the address's secret, which is why this takes the recovery phrase and
* why a nullifier should never be sent anywhere to be checked: exits publish
* nullifiers, so whoever sees yours can name your exits.
*
* For addresses `m/44'/189189189'/<account>'/<change>'/<index>'` with index in
* `start..start + addresses`, and transfer counts `first..first + count` for
* each. The BIP39 seed is stretched once for the whole run.
*
* Ported onto `qp-poseidon-core` rather than calling `qp-wormhole-circuit`,
* which would bring plonky2 into the WASM. The port is pinned to the circuit
* crate's own `Nullifier::from_preimage` by the tests.
*
* * returned vector is `addresses * count` 32-byte nullifiers: address by
* address, and by transfer count within each
* @param {string} mnemonic
* @param {string} password
* @param {number} account
* @param {number} change
* @param {number} start
* @param {number} addresses
* @param {bigint} first
* @param {number} count
* @returns {Uint8Array}
*/
export function ext_wormhole_nullifiers(mnemonic, password, account, change, start, addresses, first, count) {
const ptr0 = passStringToWasm0(mnemonic, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.ext_wormhole_nullifiers(ptr0, len0, ptr1, len1, account, change, start, addresses, first, count);
if (ret[3]) {
throw takeFromExternrefTable0(ret[2]);
}
var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v3;
}
function __wbg_get_imports() {
const import0 = {
__proto__: null,
__wbg_Error_67e7344beaa85059: function(arg0, arg1) {
const ret = Error(getStringFromWasm0(arg0, arg1));
return ret;
},
__wbindgen_init_externref_table: function() {
const table = wasm.__wbindgen_externrefs;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
},
};
return {
__proto__: null,
"./quantus_crypto_bg.js": import0,
};
}
function getArrayU8FromWasm0(ptr, len) {
ptr = ptr >>> 0;
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
}
function getStringFromWasm0(ptr, len) {
return decodeText(ptr >>> 0, len);
}
let cachedUint8ArrayMemory0 = null;
function getUint8ArrayMemory0() {
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8ArrayMemory0;
}
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1, 1) >>> 0;
getUint8ArrayMemory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8ArrayMemory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = cachedTextEncoder.encodeInto(arg, view);
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function takeFromExternrefTable0(idx) {
const value = wasm.__wbindgen_externrefs.get(idx);
wasm.__externref_table_dealloc(idx);
return value;
}
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
const MAX_SAFARI_DECODE_BYTES = 2146435072;
let numBytesDecoded = 0;
function decodeText(ptr, len) {
numBytesDecoded += len;
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
numBytesDecoded = len;
}
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
}
const cachedTextEncoder = new TextEncoder();
if (!('encodeInto' in cachedTextEncoder)) {
cachedTextEncoder.encodeInto = function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
};
}
let WASM_VECTOR_LEN = 0;
let wasmModule, wasmInstance, wasm;
function __wbg_finalize_init(instance, module) {
wasmInstance = instance;
wasm = instance.exports;
wasmModule = module;
cachedUint8ArrayMemory0 = null;
wasm.__wbindgen_start();
return wasm;
}
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (!module.ok) {
throw new Error(`failed to fetch Wasm: ${module.status} ${module.statusText} fetching '${module.url}'`);
}
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
const validResponse = expectedResponseType(module.type);
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else { throw e; }
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
function expectedResponseType(type) {
switch (type) {
case 'basic': case 'cors': case 'default': return true;
}
return false;
}
}
function initSync(module) {
if (wasm !== undefined) return wasm;
if (module !== undefined) {
if (Object.getPrototypeOf(module) === Object.prototype) {
({module} = module)
} else {
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
}
}
const imports = __wbg_get_imports();
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return __wbg_finalize_init(instance, module);
}
export { initSync };
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { accountFromPublicKey, isScheme, keypairFromMnemonic, keypairFromSeed, sign, signatureWithPublicKey, sizes, verify, WormholeBranch, wormholeAddresses, wormholeNullifiers } from './crypto.js';
export type { Keypair, Sizes } from './crypto.js';
export { initWasm, isReady } from './init.js';
export { contextForSpec, EXTRINSIC_CONTEXT, EXTRINSIC_MIN_SPEC, Scheme, SCHEME_NAME } from './scheme.js';
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { unzlibSync } from 'fflate';
import { base64Decode } from './base64.js';
import { bytes, lenOut } from './bytes.js';
import { initSync } from './generated/quantus_crypto.js';
/**
* Instantiate the WASM, synchronously, from bytes compiled into this file.
*
* Three constraints shape this, and all three rule out the obvious approach:
*
* - the background context is an **MV3 service worker**, so there is no DOM, no
* reliable `fetch` of extension-relative URLs at arbitrary times, and the
* worker can be killed and cold-started between any two messages
* - the extension CSP is `script-src 'self' 'wasm-unsafe-eval'`, which permits
* compiling WASM but not fetching it from anywhere interesting
* - callers are synchronous — `pair.sign()` in the keyring has no `await` to give
*
* So the WASM is zlib-compressed, base64'd into `bytes.js` at build time, and
* instantiated here with wasm-bindgen's `initSync`. Nothing is fetched, and the
* whole module is ready before the first call returns.
*
* Deliberately *not* using `@polkadot/wasm-bridge`: its `Bridge` implements
* wasm-bindgen 0.2.79's JS-heap ABI, and this crate is built with 0.2.128, which
* uses externref tables. See quantus/wasm#1.
*/
let initialised = false;
let initError: string | null = null;
/**
* Ensure the WASM is instantiated. Idempotent and cheap after the first call.
*
* Returns `null` on success, or the failure reason. It does not throw: a caller
* deciding whether to offer a Quantus account at all wants to ask, and an
* exception thrown from module scope in a service worker is hard to attribute.
*/
export function initWasm (): string | null {
if (initialised) {
return initError;
}
initialised = true;
try {
initSync({ module: unzlibSync(base64Decode(bytes, new Uint8Array(lenOut))) });
} catch (error) {
initError = (error as Error).message;
}
return initError;
}
/** Whether the WASM is available. Callers that can fall back should ask first. */
export function isReady (): boolean {
return initWasm() === null;
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
//! Quantus post-quantum crypto for the browser.
//!
//! Every function here delegates to the crates the Quantus runtime itself uses
//! (`qp-rusty-crystals-dilithium`, `qp-poseidon-core`, `qp-rusty-crystals-hdwallet`)
//! rather than reimplementing anything. That is the whole point: a browser wallet
//! that disagreed with the chain about a key or a signature would produce
//! perfectly well-formed output that the chain rejects, and nothing on this side
//! could tell.
#[path = "rs/hdwallet.rs"]
pub mod hdwallet;
#[path = "rs/mldsa.rs"]
pub mod mldsa;
#[path = "rs/poseidon.rs"]
pub mod poseidon;
#[path = "rs/scheme.rs"]
pub mod scheme;
#[cfg(test)]
#[path = "rs/tests.rs"]
mod tests;
+231
View File
@@ -0,0 +1,231 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
use wasm_bindgen::prelude::*;
use crate::scheme::dispatch;
/// Derive a keypair from a BIP39 mnemonic at a hardened derivation path.
///
/// Lattice keys have no public derivability, so there is no soft-junction
/// equivalent and the crate rejects any unhardened path outright. The Quantus
/// convention is:
///
/// ```text
/// m/44'/189189'/<account>'/0'/<0 for ML-DSA-87 | 1 for ML-DSA-65>'
/// ```
///
/// with the account index at the third level and the *scheme* carried in the
/// trailing index. That is unusual, and it is what `quantus-cli` and the mobile
/// wallet already use — deriving anything else produces addresses no other
/// Quantus tool can find.
///
/// The seeding matters as much as the path. This goes mnemonic → 64-byte BIP39
/// seed → HMAC-SHA512 chain keyed with the literal string `"Dilithium seed"`.
/// Substrate's own `mnemonicToMiniSecret` is a *different* derivation and is the
/// default reach in the polkadot-js codebase; using it here would yield a
/// well-formed key for an account nobody owns.
///
/// * mnemonic: BIP39 phrase, 12/15/18/21/24 words
/// * password: BIP39 passphrase; empty string for none
/// * path: hardened derivation path, e.g. `m/44'/189189'/0'/0'/1'`
/// * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
///
/// * returned vector is the secret key followed by the public key, as
/// `ext_mldsa_from_seed` returns.
#[wasm_bindgen]
pub fn ext_mldsa_derive(mnemonic: &str, password: &str, path: &str, scheme: u32) -> Result<Vec<u8>, JsError> {
mldsa_derive(mnemonic, password, path, scheme).map_err(|e| JsError::new(&e))
}
/// The body of [`ext_mldsa_derive`]. See [`crate::mldsa::mldsa_from_seed`] for why
/// this is split from its binding.
pub fn mldsa_derive(mnemonic: &str, password: &str, path: &str, scheme: u32) -> Result<Vec<u8>, String> {
// An empty passphrase and no passphrase are the same thing in BIP39, but the
// crate distinguishes `None` from `Some("")` in its signature, so normalise
// here rather than leaving each caller to pick one.
let password = if password.is_empty() {
None
} else {
Some(password)
};
dispatch!(scheme, _dsa, hd, {
let pair = hd::derive_key_from_mnemonic(mnemonic, password, path)
.map_err(alloc_error)?;
let mut out = pair.secret().to_bytes().to_vec();
out.extend_from_slice(&pair.public().to_bytes());
Ok(out)
})
}
/// Render a derivation failure as a string.
///
/// Kept separate so the error text stays whatever the crate said — a bad
/// mnemonic, an unhardened path and a path that is too deep are different
/// mistakes and a user can only fix the one they made.
fn alloc_error(e: qp_rusty_crystals_hdwallet::HDLatticeError) -> String {
format!("{e}")
}
/// The largest number of wormhole addresses one call derives. Each is a Poseidon
/// hash over an HMAC-SHA512 chain; the bound keeps a caller from asking for a
/// million of them and locking the page.
pub const WORMHOLE_MAX_BATCH: u32 = 1000;
/// Derive consecutive wormhole addresses for one account and branch.
///
/// A wormhole address is not a key. The path yields a 32-byte **secret**, and
/// the address is `poseidon(poseidon(salt ‖ secret))`; funds leave it only
/// through a ZK proof of knowing that secret. So this returns the addresses and
/// nothing else. The secrets and the intermediate `first_hash` stay inside this
/// call and are wiped on drop by the crate's sensitive types.
///
/// Paths are `m/44'/189189189'/<account>'/<change>'/<index>'`, as the mobile
/// wallet derives them: change `0` is the receive branch, `1` the change branch.
///
/// The BIP39 seed is stretched once for the whole batch. Stretching per address
/// is PBKDF2 with 2048 rounds each time, and a gap-limit window is dozens of
/// addresses.
///
/// * mnemonic: BIP39 phrase
/// * password: BIP39 passphrase; empty string for none
/// * account, change, start, count: which addresses; count <= WORMHOLE_MAX_BATCH
///
/// * returned vector is `count` 32-byte account ids, concatenated
#[wasm_bindgen]
pub fn ext_wormhole_addresses(mnemonic: &str, password: &str, account: u32, change: u32, start: u32, count: u32) -> Result<Vec<u8>, JsError> {
wormhole_addresses(mnemonic, password, account, change, start, count).map_err(|e| JsError::new(&e))
}
/// The body of [`ext_wormhole_addresses`], split from its binding for the same
/// reason as [`mldsa_derive`].
pub fn wormhole_addresses(mnemonic: &str, password: &str, account: u32, change: u32, start: u32, count: u32) -> Result<Vec<u8>, String> {
use qp_rusty_crystals_hdwallet::{generate_wormhole_from_seed, mnemonic_to_seed, SensitiveBytes64};
if count > WORMHOLE_MAX_BATCH {
return Err(format!("At most {WORMHOLE_MAX_BATCH} wormhole addresses per call, asked for {count}"));
}
if start.checked_add(count).is_none_or(|end| end > 0x8000_0000) {
return Err(format!("Wormhole address indices must stay below 2^31, asked for {start} + {count}"));
}
let password = if password.is_empty() {
None
} else {
Some(password)
};
let mut seed = SensitiveBytes64::zeroed();
mnemonic_to_seed(mnemonic.to_string(), password, &mut seed).map_err(alloc_error)?;
let mut out = Vec::with_capacity(count as usize * 32);
for index in start..start + count {
let path = format!("m/44'/189189189'/{account}'/{change}'/{index}'");
let pair = generate_wormhole_from_seed(&seed, &path).map_err(alloc_error)?;
out.extend_from_slice(pair.address());
}
Ok(out)
}
/// Salt the chain's nullifier derivation starts from: `NULLIFIER_SALT` in
/// `qp-wormhole-circuit`.
const NULLIFIER_SALT: &str = "~nullif~";
/// The largest number of nullifiers one call computes.
pub const NULLIFIER_MAX_BATCH: u32 = 100_000;
/// Nullifiers for a run of wormhole addresses' deposits, by transfer count.
///
/// A deposit to a wormhole address is spent when its nullifier is in
/// `Wormhole::UsedNullifiers`. The nullifier is
///
/// ```text
/// poseidon2(poseidon2(salt("~nullif~") ‖ secret ‖ transfer_count))
/// ```
///
/// where `transfer_count` is the address's counter when the deposit landed. It
/// needs the address's secret, which is why this takes the recovery phrase and
/// why a nullifier should never be sent anywhere to be checked: exits publish
/// nullifiers, so whoever sees yours can name your exits.
///
/// For addresses `m/44'/189189189'/<account>'/<change>'/<index>'` with index in
/// `start..start + addresses`, and transfer counts `first..first + count` for
/// each. The BIP39 seed is stretched once for the whole run.
///
/// Ported onto `qp-poseidon-core` rather than calling `qp-wormhole-circuit`,
/// which would bring plonky2 into the WASM. The port is pinned to the circuit
/// crate's own `Nullifier::from_preimage` by the tests.
///
/// * returned vector is `addresses * count` 32-byte nullifiers: address by
/// address, and by transfer count within each
#[wasm_bindgen]
#[allow(clippy::too_many_arguments)]
pub fn ext_wormhole_nullifiers(mnemonic: &str, password: &str, account: u32, change: u32, start: u32, addresses: u32, first: u64, count: u32) -> Result<Vec<u8>, JsError> {
wormhole_nullifiers(mnemonic, password, account, change, start, addresses, first, count).map_err(|e| JsError::new(&e))
}
/// The body of [`ext_wormhole_nullifiers`].
#[allow(clippy::too_many_arguments)]
pub fn wormhole_nullifiers(mnemonic: &str, password: &str, account: u32, change: u32, start: u32, addresses: u32, first: u64, count: u32) -> Result<Vec<u8>, String> {
use qp_rusty_crystals_hdwallet::{generate_wormhole_from_seed, mnemonic_to_seed, SensitiveBytes64};
let total = addresses as u64 * count as u64;
if total > NULLIFIER_MAX_BATCH as u64 {
return Err(format!("At most {NULLIFIER_MAX_BATCH} nullifiers per call, asked for {total}"));
}
if start.checked_add(addresses).is_none_or(|end| end > 0x8000_0000) {
return Err(format!("Wormhole address indices must stay below 2^31, asked for {start} + {addresses}"));
}
first.checked_add(count as u64).ok_or("Transfer counts overflow")?;
let password = if password.is_empty() {
None
} else {
Some(password)
};
let mut seed = SensitiveBytes64::zeroed();
mnemonic_to_seed(mnemonic.to_string(), password, &mut seed).map_err(alloc_error)?;
let mut out = Vec::with_capacity(total as usize * 32);
for index in start..start + addresses {
let path = format!("m/44'/189189189'/{account}'/{change}'/{index}'");
let pair = generate_wormhole_from_seed(&seed, &path).map_err(alloc_error)?;
for transfer_count in first..first + count as u64 {
out.extend_from_slice(&nullifier(pair.secret().as_bytes(), transfer_count));
}
}
Ok(out)
}
/// One nullifier from a wormhole secret and a transfer count.
pub fn nullifier(secret: &[u8; 32], transfer_count: u64) -> [u8; 32] {
use qp_poseidon_core::{
hash_twice,
serialization::{bytes_to_digest_lossy, string_to_felts, u64_to_felts},
};
let salt = string_to_felts(NULLIFIER_SALT);
let secret_felts = bytes_to_digest_lossy(secret);
let count_felts = u64_to_felts(transfer_count);
let mut preimage = Vec::with_capacity(salt.len() + secret_felts.len() + count_felts.len());
preimage.extend_from_slice(&salt);
preimage.extend_from_slice(&secret_felts);
preimage.extend_from_slice(&count_felts);
hash_twice(&preimage)
}
+164
View File
@@ -0,0 +1,164 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
use wasm_bindgen::prelude::*;
use crate::scheme::dispatch;
/// Key and signature sizes for a parameter set, as
/// `[public, secret, signature, signature_with_public]`.
///
/// Exported so that nothing on the JS side has to hardcode 1952/4032/3309/5261 or
/// 2592/4896/4627/7219. Those numbers are consensus-critical — the runtime reads a
/// fixed-size array off the wire — and a JS constant that drifted from the crate
/// would mis-frame every byte after the signature while looking entirely healthy.
/// Ask the crate instead.
///
/// * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
///
/// * returned vector is four u32 lengths, little-endian, 16 bytes total.
#[wasm_bindgen]
pub fn ext_mldsa_sizes(scheme: u32) -> Vec<u8> {
dispatch!(scheme, dsa, _hd, {
let sizes: [u32; 4] = [
dsa::PUBLICKEYBYTES as u32,
dsa::SECRETKEYBYTES as u32,
dsa::SIGNBYTES as u32,
(dsa::SIGNBYTES + dsa::PUBLICKEYBYTES) as u32,
];
sizes.iter().flat_map(|n| n.to_le_bytes()).collect()
})
}
/// Whether `scheme` names a parameter set this build supports.
///
/// `dispatch!` falls back to ML-DSA-87 for anything unrecognised, which is the
/// right default but a poor way to discover a typo. Callers that accept a scheme
/// from storage or from a user should check here first.
#[wasm_bindgen]
pub fn ext_mldsa_is_scheme(scheme: u32) -> bool {
scheme == crate::scheme::ML_DSA_87 || scheme == crate::scheme::ML_DSA_65
}
/// Generate a keypair from 32 bytes of entropy.
///
/// This is FIPS 204 `ML-DSA.KeyGen_internal` with no Quantus-specific step: the
/// crate expands the seed as `SHAKE256(seed ‖ k ‖ )`, so the parameter set is
/// absorbed into the expansion and the same 32 bytes yield independent keys per
/// scheme. That is why the dev accounts (`[0u8; 32]`, `[1u8; 32]`, `[2u8; 32]`)
/// and HD-derived accounts can share this one entry point.
///
/// * seed: UIntArray with 32 elements
/// * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
///
/// * returned vector is the secret key followed by the public key, matching the
/// ordering `ext_ed_from_seed` uses. Split it at the secret length from
/// `ext_mldsa_sizes`.
#[wasm_bindgen]
pub fn ext_mldsa_from_seed(seed: &[u8], scheme: u32) -> Result<Vec<u8>, JsError> {
mldsa_from_seed(seed, scheme).map_err(|e| JsError::new(&e))
}
/// The body of [`ext_mldsa_from_seed`], without the binding layer.
///
/// Split out because `JsError` cannot be constructed on a non-wasm target — it
/// panics with "cannot call wasm-bindgen imported functions on non-wasm targets" —
/// so anything that returns one is untestable by `cargo test`. The error paths are
/// exactly what most needs testing, so the logic lives here and the exported
/// wrapper does nothing but translate.
pub fn mldsa_from_seed(seed: &[u8], scheme: u32) -> Result<Vec<u8>, String> {
if seed.len() != 32 {
return Err("expected a 32 byte seed".into());
}
// `SensitiveBytes32::from` takes the buffer mutably and the crate zeroes it
// after use, so the copy we hand it is destroyed rather than left on the
// stack. Do not replace this with a by-value clone of `seed`.
let mut entropy = [0u8; 32];
entropy.copy_from_slice(seed);
let mut entropy = qp_rusty_crystals_dilithium::SensitiveBytes32::from(&mut entropy);
dispatch!(scheme, dsa, _hd, {
let pair = dsa::Keypair::generate(&mut entropy);
let mut out = pair.secret().to_bytes().to_vec();
out.extend_from_slice(&pair.public().to_bytes());
Ok(out)
})
}
/// Sign a message under a FIPS 204 context.
///
/// Signing is deterministic — no hedging randomness — because that is what the
/// runtime does (`hedge: None`), and a wallet that hedged would produce a
/// different signature each time for the same input, which makes the
/// byte-for-byte agreement tests in quantus/wasm#2 impossible to write.
///
/// `ctx` is domain separation and it is **not** optional in practice: extrinsics
/// on spec >= 148 are verified under `QUANTUS_EXTRINSIC`, earlier specs under the
/// empty context, and a signature made under the wrong one is valid, rejected by
/// the chain, and indistinguishable locally. The caller chooses; this function
/// does not guess.
///
/// * secret: UIntArray, secret-key length for the scheme
/// * public: UIntArray, public-key length for the scheme
/// * message: arbitrary length UIntArray
/// * ctx: UIntArray, at most 255 elements; empty for no context
/// * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
///
/// * returned vector is the signature alone. The runtime's wire format is
/// `signature ‖ public`; concatenating is the caller's job because only the
/// caller knows whether it wants the wire form or the bare signature.
#[wasm_bindgen]
pub fn ext_mldsa_sign(secret: &[u8], public: &[u8], message: &[u8], ctx: &[u8], scheme: u32) -> Result<Vec<u8>, JsError> {
mldsa_sign(secret, public, message, ctx, scheme).map_err(|e| JsError::new(&e))
}
/// The body of [`ext_mldsa_sign`]. See [`mldsa_from_seed`] for why this is split.
pub fn mldsa_sign(secret: &[u8], public: &[u8], message: &[u8], ctx: &[u8], scheme: u32) -> Result<Vec<u8>, String> {
if ctx.len() > 255 {
return Err("context must be at most 255 bytes".into());
}
dispatch!(scheme, dsa, _hd, {
// `from_parts` re-derives the public key from the secret and rejects a
// mismatch, so a corrupted or mixed-up pair fails here rather than
// producing a signature that silently will not verify.
let secret = dsa::SecretKey::from_bytes(secret)
.map_err(|_| "invalid secret key".to_string())?;
let public = dsa::PublicKey::from_bytes(public)
.map_err(|_| "invalid public key".to_string())?;
let pair = dsa::Keypair::from_parts(secret, public)
.map_err(|_| "secret and public key do not correspond".to_string())?;
pair
.sign(message, Some(ctx), None)
.map(|sig| sig.to_vec())
.map_err(|_| "signing failed".to_string())
})
}
/// Verify a signature against a message and public key under a context.
///
/// * public: UIntArray, public-key length for the scheme
/// * message: arbitrary length UIntArray
/// * signature: UIntArray, signature length for the scheme
/// * ctx: UIntArray, at most 255 elements; empty for no context
/// * scheme: 0 for ML-DSA-87, 1 for ML-DSA-65
#[wasm_bindgen]
pub fn ext_mldsa_verify(public: &[u8], message: &[u8], signature: &[u8], ctx: &[u8], scheme: u32) -> bool {
if ctx.len() > 255 {
return false;
}
dispatch!(scheme, dsa, _hd, {
match dsa::PublicKey::from_bytes(public) {
Ok(public) => public.verify(message, signature, Some(ctx)),
Err(_) => false
}
})
}
@@ -0,0 +1,22 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
use wasm_bindgen::prelude::*;
/// Poseidon2-over-Goldilocks hash of arbitrary bytes.
///
/// This is the account-id derivation. On Substrate an `AccountId32` *is* the
/// public key; on Quantus it is `hash_bytes(public_key)`, which is why a Quantus
/// signature has to carry its public key along — the address cannot give it back.
///
/// `qp_poseidon_core::hash_bytes` is `IdentifyAccount for DilithiumSigner` in the
/// runtime, so this is the same function the chain uses to decide who signed
/// something, reached through the same crate rather than a port of it.
///
/// * data: arbitrary length UIntArray
///
/// * returned vector is 32 bytes.
#[wasm_bindgen]
pub fn ext_poseidon_hash(data: &[u8]) -> Vec<u8> {
qp_poseidon_core::hash_bytes(data).to_vec()
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
//! Which ML-DSA parameter set a call refers to.
//!
//! The selector is the chain's own signature-enum variant index, not a private
//! numbering: `DilithiumSignatureScheme::Dilithium87` is variant 0 and
//! `Dilithium65` is variant 1, and that byte is what a signed extrinsic carries
//! on the wire. Reusing it here means the number threaded through this API is
//! the number that ends up in the extrinsic, so there is no mapping table to get
//! backwards between here and `TYPE_PREFIX` in the keyring.
/// ML-DSA-87 — the legacy scheme, used by accounts created before the scheme was
/// recorded, and by the dev-genesis accounts.
pub const ML_DSA_87: u32 = 0;
/// ML-DSA-65 — what new accounts use.
pub const ML_DSA_65: u32 = 1;
/// Runs `$body` with `$dsa` and `$hd` bound to the parameter-set modules named by
/// `$scheme`.
///
/// An unrecognised selector resolves to ML-DSA-87 rather than panicking: 0 is the
/// legacy scheme and the safest thing an out-of-range value can mean. Callers
/// that care validate first — see `ext_mldsa_is_scheme`.
macro_rules! dispatch {
($scheme:expr, $dsa:ident, $hd:ident, $body:block) => {
match $scheme {
$crate::scheme::ML_DSA_65 => {
#[allow(unused_imports)]
use qp_rusty_crystals_dilithium::ml_dsa_65 as $dsa;
#[allow(unused_imports)]
use qp_rusty_crystals_hdwallet::ml_dsa_65 as $hd;
$body
},
_ => {
#[allow(unused_imports)]
use qp_rusty_crystals_dilithium::ml_dsa_87 as $dsa;
#[allow(unused_imports)]
use qp_rusty_crystals_hdwallet::ml_dsa_87 as $hd;
$body
},
}
};
}
pub(crate) use dispatch;
+303
View File
@@ -0,0 +1,303 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
//! Conformance against the chain, not against ourselves.
//!
//! Every expected value here came from an independent implementation — the
//! `quantus` CLI 2.2.2 — and not from running this crate and writing down what
//! it said. A test that pins our own output would keep passing through exactly
//! the kind of drift these exist to catch.
//!
//! Addresses are pinned as raw account-id bytes rather than SS58 strings so this
//! file needs no base58 dependency; the SS58 rendering at prefix 189 is pinned on
//! the JS side, which is where it is actually used.
use crate::{hdwallet::{mldsa_derive, wormhole_addresses}, mldsa::*, poseidon::*, scheme::*};
/// FIPS 204 context for on-chain extrinsic signatures, spec >= 148.
/// `chain:primitives/dilithium-crypto/src/signing_context.rs`.
const EXTRINSIC: &[u8] = b"QUANTUS_EXTRINSIC";
fn account_of(seed_fill: u8, scheme: u32) -> Vec<u8> {
let pair = mldsa_from_seed(&[seed_fill; 32], scheme).expect("keygen");
let secret_len = secret_len(scheme);
ext_poseidon_hash(&pair[secret_len..])
}
fn secret_len(scheme: u32) -> usize {
let sizes = ext_mldsa_sizes(scheme);
u32::from_le_bytes(sizes[4..8].try_into().unwrap()) as usize
}
#[test]
fn sizes_match_the_parameter_sets() {
// [public, secret, signature, signature_with_public]
let s87: Vec<u32> = ext_mldsa_sizes(ML_DSA_87).chunks(4).map(|c| u32::from_le_bytes(c.try_into().unwrap())).collect();
let s65: Vec<u32> = ext_mldsa_sizes(ML_DSA_65).chunks(4).map(|c| u32::from_le_bytes(c.try_into().unwrap())).collect();
assert_eq!(s87, vec![2592, 4896, 4627, 7219]);
assert_eq!(s65, vec![1952, 4032, 3309, 5261]);
}
/// The dev accounts endowed at genesis, from `chain:primitives/dilithium-crypto/src/pair.rs`.
///
/// Expected values are the account ids behind the SS58 addresses that
/// `quantus developer create-test-wallets` prints:
///
/// ```text
/// crystal_alice qzk1Nxai3dZD9Cn5kwGcgL6mKxsfxwqdis7kDQJ52aJS2vSn7
/// dilithium_bob qzkYEQv8tQsmniZYdame3Cku18RL5g9bGK9Pdydq5TMPdpE3y
/// crystal_charlie qzntBpmqHZF1jxC8KJKpuxcYuHST892jyXBqRctpAxd1WQ9BL
/// ```
///
/// They are ML-DSA-87 and come from the seed directly with no HD derivation, so
/// this pins the legacy scheme and the raw-seed path in one go.
#[test]
fn dev_account_ids_match_the_cli() {
assert_eq!(
hex(&account_of(0, ML_DSA_87)),
"1883df2ae47d1fd428a6b8237ad7b59cf0facccaacac4541ef7758be44b3c333",
"crystal_alice"
);
assert_eq!(
hex(&account_of(1, ML_DSA_87)),
"300bb607ba60e89461d2f9005668231ceb30237b33db53a614164b8590965519",
"dilithium_bob"
);
assert_eq!(
hex(&account_of(2, ML_DSA_87)),
"97bc5f2db1efa23fb71f6737fcb26e41e448aff07447011369df81ce43555465",
"crystal_charlie"
);
}
/// The same 32 bytes must give different keys per parameter set — FIPS 204
/// absorbs `(k, )` into the seed expansion. If these ever collided it would mean
/// the scheme selector was being ignored somewhere.
#[test]
fn schemes_are_independent_for_the_same_seed() {
assert_ne!(account_of(0, ML_DSA_87), account_of(0, ML_DSA_65));
}
#[test]
fn signs_and_verifies_under_the_extrinsic_context() {
for scheme in [ML_DSA_87, ML_DSA_65] {
let pair = mldsa_from_seed(&[7u8; 32], scheme).expect("keygen");
let (secret, public) = pair.split_at(secret_len(scheme));
let message = b"the payload the chain will see";
let signature = mldsa_sign(secret, public, message, EXTRINSIC, scheme).expect("sign");
assert!(ext_mldsa_verify(public, message, &signature, EXTRINSIC, scheme));
// The whole point of the context. A signature made for an extrinsic must
// not verify as anything else, and vice versa — this is what makes the
// spec-148 boundary detectable instead of a silent chain rejection.
assert!(!ext_mldsa_verify(public, message, &signature, b"", scheme));
assert!(!ext_mldsa_verify(public, b"tampered", &signature, EXTRINSIC, scheme));
}
}
/// The runtime signs with `hedge: None`. If this crate ever introduced hedging
/// randomness the golden vectors in quantus/wasm#2 would become unwritable, and
/// nothing else would notice.
#[test]
fn signing_is_deterministic() {
let pair = mldsa_from_seed(&[9u8; 32], ML_DSA_65).expect("keygen");
let (secret, public) = pair.split_at(secret_len(ML_DSA_65));
let once = mldsa_sign(secret, public, b"m", EXTRINSIC, ML_DSA_65).expect("sign");
let twice = mldsa_sign(secret, public, b"m", EXTRINSIC, ML_DSA_65).expect("sign");
assert_eq!(once, twice);
}
#[test]
fn rejects_bad_input() {
assert!(mldsa_from_seed(&[0u8; 16], ML_DSA_65).is_err(), "short seed");
let pair = mldsa_from_seed(&[1u8; 32], ML_DSA_65).expect("keygen");
let (secret, public) = pair.split_at(secret_len(ML_DSA_65));
assert!(mldsa_sign(secret, public, b"m", &[0u8; 256], ML_DSA_65).is_err(), "context > 255");
assert!(mldsa_sign(&secret[1..], public, b"m", EXTRINSIC, ML_DSA_65).is_err(), "truncated secret");
// A pair whose halves do not correspond must fail at import rather than
// produce a signature that silently will not verify.
let other = mldsa_from_seed(&[2u8; 32], ML_DSA_65).expect("keygen");
let other_public = &other[secret_len(ML_DSA_65)..];
assert!(mldsa_sign(secret, other_public, b"m", EXTRINSIC, ML_DSA_65).is_err(), "mismatched pair");
}
/// The well-known Substrate development phrase. Public by design — it is in
/// polkadot-sdk, in polkadot-js, and in every tutorial — so pinning it here
/// commits no secret. Any account it derives is assumed compromised.
const DEV_PHRASE: &str = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// HD derivation at the Quantus BIP44 path, cross-checked against
/// `quantus wallet import --mnemonic-file <DEV_PHRASE> --scheme <s>`, which
/// printed:
///
/// ```text
/// ml-dsa-65 m/44'/189189'/0'/0'/1' qzq29m9WvneDAeXbtgueKCREtNe1rVVs6bXSMLmjr6shqvwq6
/// ml-dsa-87 m/44'/189189'/0'/0'/0' qzjrYTUnnE5NduTZKxe9dESCMTZg7nTueKM3bwhnkRdD1iYV4
/// ```
///
/// This pins the whole derivation chain at once: BIP39 to a 64-byte seed (*not*
/// Substrate's `mnemonicToMiniSecret`), the HMAC-SHA512 walk keyed with
/// "Dilithium seed", the trailing hardened index carrying the scheme, and the
/// Poseidon2 account-id hash on the end.
#[test]
fn hd_derivation_matches_the_cli() {
let cases = [
(ML_DSA_65, "m/44'/189189'/0'/0'/1'", "f647dbdefebcfcf726ba078a83481ffc6f4f33004fdfb4cedacf5a5391bc8f00"),
(ML_DSA_87, "m/44'/189189'/0'/0'/0'", "11c6a314e003cdee3dc51cf6569175360141578d054c38d7a70840a65cc0e990")
];
for (scheme, path, expected) in cases {
let pair = mldsa_derive(DEV_PHRASE, "", path, scheme).expect("derive");
let account = ext_poseidon_hash(&pair[secret_len(scheme)..]);
assert_eq!(hex(&account), expected, "{path}");
}
}
/// Lattice keys have no public derivability, so the crate rejects unhardened
/// paths outright rather than inventing a meaning for them. A wallet that
/// silently hardened a soft path would put funds at an address the user did not
/// ask for.
#[test]
fn derivation_rejects_bad_input() {
assert!(mldsa_derive(DEV_PHRASE, "", "m/44'/189189'/0'/0'/1", ML_DSA_65).is_err(), "unhardened");
assert!(mldsa_derive("not a mnemonic at all", "", "m/44'/189189'/0'/0'/1'", ML_DSA_65).is_err(), "bad phrase");
assert!(mldsa_derive(DEV_PHRASE, "", "not a path", ML_DSA_65).is_err(), "bad path");
}
/// A BIP39 passphrase must change the result, and an empty string must mean
/// "no passphrase" rather than "a passphrase that happens to be empty" — the
/// two are the same in BIP39 but the crate's signature distinguishes them, and
/// normalising in the wrong direction would silently fork every address.
#[test]
fn passphrase_is_honoured_and_empty_means_none() {
let path = "m/44'/189189'/0'/0'/1'";
let none = mldsa_derive(DEV_PHRASE, "", path, ML_DSA_65).expect("derive");
let with = mldsa_derive(DEV_PHRASE, "hunter2", path, ML_DSA_65).expect("derive");
assert_ne!(none, with);
}
#[test]
fn scheme_validation() {
assert!(ext_mldsa_is_scheme(ML_DSA_87));
assert!(ext_mldsa_is_scheme(ML_DSA_65));
assert!(!ext_mldsa_is_scheme(2));
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
/// The chain node's own wormhole vector, `node/src/tests/data/quantus_key_test_data.rs`:
/// `TEST_MNEMONIC` at `m/44'/189189189'/0'/0'/0'` is `TEST_WORMHOLE_ADDRESS`,
/// `qzpWh4AEtsgCyEbv4WBgFWnB9bcdF2L2jVDuyjXP9mSTyBaeU`. The mobile wallet's SDK
/// pins the same pair (`generate_keys_test.dart`), so three implementations agree.
#[test]
fn wormhole_address_matches_the_node() {
const MNEMONIC: &str = "orchard answer curve patient visual flower maze noise retreat penalty cage small earth domain scan pitch bottom crunch theme club client swap slice raven";
let batch = wormhole_addresses(MNEMONIC, "", 0, 0, 0, 3).expect("derive");
assert_eq!(batch.len(), 96);
assert_eq!(hex(&batch[..32]), "dfcfd6e59c75d208e84f54a887537bcf7b04265790ec79960bf49de123404d0e");
// A batch is the same as asking for each index on its own: the seed is
// stretched once, but every address still gets its own path.
for i in 0..3u32 {
let one = wormhole_addresses(MNEMONIC, "", 0, 0, i, 1).expect("derive one");
assert_eq!(one, batch[(i as usize * 32)..(i as usize + 1) * 32].to_vec());
}
// Receive and change branches, and account indices, are different addresses.
assert_ne!(wormhole_addresses(MNEMONIC, "", 0, 1, 0, 1).unwrap(), batch[..32].to_vec());
assert_ne!(wormhole_addresses(MNEMONIC, "", 1, 0, 0, 1).unwrap(), batch[..32].to_vec());
}
#[test]
fn wormhole_addresses_refuse_unbounded_requests() {
const MNEMONIC: &str = "orchard answer curve patient visual flower maze noise retreat penalty cage small earth domain scan pitch bottom crunch theme club client swap slice raven";
assert!(wormhole_addresses(MNEMONIC, "", 0, 0, 0, 1001).is_err());
assert!(wormhole_addresses(MNEMONIC, "", 0, 0, 0x7fff_ffff, 2).is_err());
assert!(wormhole_addresses("not a mnemonic", "", 0, 0, 0, 1).is_err());
}
/// The port of the nullifier agrees with the chain's circuit crate.
///
/// `qp_wormhole_circuit::nullifier::Nullifier::from_preimage` is what the proof
/// commits to and what `Wormhole::UsedNullifiers` records, so it is the
/// reference. Secrets span the edge the lossy 8-bytes-per-felt encoding cares
/// about (limbs at and above the Goldilocks prime), and transfer counts span
/// both 32-bit limbs.
#[test]
fn nullifier_matches_the_circuit() {
use crate::hdwallet::nullifier;
use qp_wormhole_circuit::nullifier::Nullifier;
use qp_zk_circuits_common::utils::{digest_to_bytes, BytesDigest};
let secrets: Vec<[u8; 32]> = vec![
[0u8; 32],
[0xff; 32],
core::array::from_fn(|i| i as u8),
core::array::from_fn(|i| (i as u8).wrapping_mul(97).wrapping_add(13)),
// every limb is the Goldilocks prime 2^64 - 2^32 + 1, big-endian
[0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01].repeat(4).try_into().unwrap(),
];
let counts = [0u64, 1, 41_683, u32::MAX as u64, 1u64 << 32, u64::MAX];
let mut compared = 0;
for secret in &secrets {
for &count in &counts {
let digest = BytesDigest::try_from(*secret);
// The circuit's BytesDigest refuses a non-canonical limb; where it
// does, the chain can never produce that secret's nullifier either.
let Ok(digest) = digest else { continue };
let expected = digest_to_bytes(Nullifier::from_preimage(digest, count).hash);
assert_eq!(hex(&nullifier(secret, count)), hex(expected.as_ref()), "secret {} count {count}", hex(secret));
compared += 1;
}
}
// Skipping is for the non-canonical edge only; a test that compared
// nothing would pass just as well.
assert!(compared >= 18, "only {compared} cases compared");
}
#[test]
fn wormhole_nullifiers_follow_each_address_secret() {
use crate::hdwallet::{nullifier, wormhole_nullifiers};
use qp_rusty_crystals_hdwallet::derive_wormhole_from_mnemonic;
const MNEMONIC: &str = "orchard answer curve patient visual flower maze noise retreat penalty cage small earth domain scan pitch bottom crunch theme club client swap slice raven";
// addresses 2 and 3 on the change branch, transfer counts 5..8
let batch = wormhole_nullifiers(MNEMONIC, "", 0, 1, 2, 2, 5, 3).unwrap();
assert_eq!(batch.len(), 2 * 3 * 32);
for (a, index) in [2u32, 3].into_iter().enumerate() {
let pair = derive_wormhole_from_mnemonic(MNEMONIC, None, &format!("m/44'/189189189'/0'/1'/{index}'")).unwrap();
for c in 0..3usize {
let at = (a * 3 + c) * 32;
assert_eq!(batch[at..at + 32].to_vec(), nullifier(pair.secret().as_bytes(), 5 + c as u64).to_vec(), "address {index} count {}", 5 + c);
}
}
assert!(wormhole_nullifiers(MNEMONIC, "", 0, 0, 0, 40, 0, 2_501).is_err());
assert!(wormhole_nullifiers(MNEMONIC, "", 0, 0, 0, 1, u64::MAX, 2).is_err());
assert!(wormhole_nullifiers(MNEMONIC, "", 0, 0, 0x7fff_ffff, 2, 0, 1).is_err());
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* Which ML-DSA parameter set a call refers to.
*
* These are the chain's own `DilithiumSignatureScheme` variant indices, not a
* private numbering — the value here is the byte a signed extrinsic carries on
* the wire. Keeping them identical means the keyring's `TYPE_PREFIX` is the
* identity function on a scheme, with no table to get backwards.
*/
export enum Scheme {
/** ML-DSA-87. Legacy: accounts created before the scheme was recorded, and the dev-genesis accounts. */
MlDsa87 = 0,
/** ML-DSA-65. What new accounts use. */
MlDsa65 = 1
}
/**
* The name each scheme is stored under, matching `quantus-cli` and the mobile
* wallet so a wallet exported from one tool imports into another.
*/
export const SCHEME_NAME: Record<Scheme, string> = {
[Scheme.MlDsa87]: 'ml-dsa-87',
[Scheme.MlDsa65]: 'ml-dsa-65'
};
/**
* FIPS 204 context for on-chain extrinsic signatures.
*
* Only from spec 148 onward — earlier runtimes verify under the empty context,
* and a signature made under the wrong one is valid, rejected by the chain, and
* indistinguishable locally. Callers pass the spec version and get the right
* answer from {@link contextForSpec}; nothing here guesses.
*/
export const EXTRINSIC_CONTEXT = new TextEncoder().encode('QUANTUS_EXTRINSIC');
/** First spec version that verifies extrinsics under {@link EXTRINSIC_CONTEXT}. */
export const EXTRINSIC_MIN_SPEC = 148;
/** The signing context a runtime at `specVersion` expects. */
export function contextForSpec (specVersion: number): Uint8Array {
return specVersion >= EXTRINSIC_MIN_SPEC
? EXTRINSIC_CONTEXT
: new Uint8Array();
}
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
//
// Consumes the *built* package exactly as quantus/common will — a plain import of
// build output, nothing reaching into src or poking the wasm by hand. Run after
// ./scripts/build-quantus.sh.
//
// This exists because the unit tests in src/rs/tests.rs cannot catch packaging
// faults. A wasm that is valid before `wasm-opt` and broken after it passes every
// cargo test and fails here, which is exactly how binaryen 105's mishandling of
// externref tables was found.
import {
accountFromPublicKey, contextForSpec, EXTRINSIC_MIN_SPEC, initWasm,
isReady, keypairFromMnemonic, keypairFromSeed, Scheme, SCHEME_NAME,
sign, signatureWithPublicKey, sizes, verify, WormholeBranch, wormholeAddresses, wormholeNullifiers
} from '@quantus/crypto';
let fail = 0;
const eq = (l, g, w) => { const ok = String(g) === String(w); if (!ok) fail++;
console.log(`${ok ? 'PASS' : 'FAIL'} ${l}`); if (!ok) console.log(` got ${g}\n want ${w}`); };
eq('initWasm() returns no error', initWasm(), 'null');
eq('isReady()', isReady(), true);
const s65 = sizes(Scheme.MlDsa65);
eq('ML-DSA-65 sizes', JSON.stringify(s65), '{"publicKey":1952,"secretKey":4032,"signature":3309,"signatureWithPublicKey":5261}');
eq('scheme name', SCHEME_NAME[Scheme.MlDsa65], 'ml-dsa-65');
eq('variant byte is the enum value', Scheme.MlDsa87, 0);
// crystal_alice, via the public API only
const pair87 = keypairFromSeed(new Uint8Array(32), Scheme.MlDsa87);
eq('crystal_alice account id',
Buffer.from(accountFromPublicKey(pair87.publicKey)).toString('hex'),
'1883df2ae47d1fd428a6b8237ad7b59cf0facccaacac4541ef7758be44b3c333');
// HD derivation, dev phrase, ML-DSA-65 default path
const DEV = 'bottom drive obey lake curtain smoke basket hold race lonely fit walk';
const hd = keypairFromMnemonic(DEV, '', "m/44'/189189'/0'/0'/1'", Scheme.MlDsa65);
eq('dev phrase account id (ML-DSA-65)',
Buffer.from(accountFromPublicKey(hd.publicKey)).toString('hex'),
'f647dbdefebcfcf726ba078a83481ffc6f4f33004fdfb4cedacf5a5391bc8f00');
// the signing-context boundary
const msg = new TextEncoder().encode('extrinsic payload');
const ctx = contextForSpec(EXTRINSIC_MIN_SPEC);
const sig = sign(msg, hd, ctx, Scheme.MlDsa65);
eq('signature length', sig.length, s65.signature);
eq('verifies at spec 148', verify(msg, sig, hd.publicKey, ctx, Scheme.MlDsa65), true);
eq('does NOT verify at spec 147', verify(msg, sig, hd.publicKey, contextForSpec(147), Scheme.MlDsa65), false);
eq('contextForSpec(147) is empty', contextForSpec(147).length, 0);
// the wire form
eq('sig || pk length', signatureWithPublicKey(sig, hd.publicKey).length, s65.signatureWithPublicKey);
// wormhole: the chain node's TEST_WORMHOLE_ADDRESS (qzpWh4AEtsgCyEbv4WBgFWnB9bcdF2L2jVDuyjXP9mSTyBaeU)
const NODE_PHRASE = 'orchard answer curve patient visual flower maze noise retreat penalty cage small earth domain scan pitch bottom crunch theme club client swap slice raven';
const wh = wormholeAddresses(NODE_PHRASE, '', 0, WormholeBranch.Receive, 0, 2);
eq('wormhole address count', wh.length, 2);
eq('wormhole receive 0 is the node test address',
Buffer.from(wh[0]).toString('hex'),
'dfcfd6e59c75d208e84f54a887537bcf7b04265790ec79960bf49de123404d0e');
eq('change branch differs', Buffer.from(wormholeAddresses(NODE_PHRASE, '', 0, WormholeBranch.Change, 0, 1)[0]).toString('hex') !== Buffer.from(wh[0]).toString('hex'), true);
// nullifiers: shape, determinism, and the cost of a wallet account's precompute
const n = wormholeNullifiers(NODE_PHRASE, '', 0, WormholeBranch.Receive, 0, 2, 7, 3);
eq('nullifier shape', `${n.length}x${n[0].length}x${n[0][0].length}`, '2x3x32');
eq('nullifiers differ by count', Buffer.from(n[0][0]).equals(Buffer.from(n[0][1])), false);
eq('nullifiers differ by address', Buffer.from(n[0][0]).equals(Buffer.from(n[1][0])), false);
eq('a sub-range agrees', Buffer.from(wormholeNullifiers(NODE_PHRASE, '', 0, WormholeBranch.Receive, 1, 1, 8, 1)[0][0]).toString('hex'), Buffer.from(n[1][1]).toString('hex'));
const t0 = performance.now();
wormholeNullifiers(NODE_PHRASE, '', 0, WormholeBranch.Receive, 0, 20, 0, 256);
wormholeNullifiers(NODE_PHRASE, '', 0, WormholeBranch.Change, 0, 20, 0, 256);
console.log(` 40 addresses x 256 counts: ${Math.round(performance.now() - t0)} ms`);
process.exit(fail ? 1 : 0);
@@ -0,0 +1 @@
vendor/
@@ -0,0 +1,69 @@
# Browser probes
`cargo test` and the consumer test both run in node, and node is neither a
browser nor a service worker. These two probes cover what node cannot: does the
WASM instantiate with **no DOM**, under the **extension's own CSP**, and what does
a cold start cost.
Build first with `./scripts/build-quantus.sh`, stage `vendor/` (below), then run
`node serve.mjs` from this directory.
## `index.html` + `worker.js` — automated
A module Worker, served with the exact `extension_pages` CSP from both extension
manifests:
```
script-src 'self' 'wasm-unsafe-eval'; object-src 'self'
```
A module Worker has no `window` and no `document`, which is the property that
matters — an MV3 service worker has neither either. Result on Firefox:
```
hasDOM: false hasWindow: false
initWasm: ok (9.0 ms cold)
keygen: 1.0 ms
account: matches quantus-cli
sign: 3.0 ms (4627 bytes)
verify: 1.0 ms ok
ctx sep: ok (rejected under spec-147 ctx)
```
9 ms to base64-decode 146 KB, zlib-inflate it to 234 KB and instantiate. That is
the number the MV3 lifetime question turns on: a service worker killed between
messages pays this on every wake, and 9 ms is not a problem. Those are ML-DSA-87
timings — the larger parameter set — so ML-DSA-65 is cheaper still.
The CSP is genuinely enforced here, not merely declared: an earlier version of
this page used an inline `<script>` and Firefox blocked it, which is why
`main.js` exists as a separate file.
## `manifest.json` + `sw.js` — manual
The real thing: an MV3 extension whose background service worker imports the
package at module scope and signs once. Loading an unpacked extension needs an OS
file dialog, so this cannot be driven from a script — load it by hand via
`chrome://extensions` → Developer mode → Load unpacked, then click the toolbar
icon.
It covers what the Worker probe cannot: `chrome.runtime` messaging, and the
actual MV3 kill-and-restart lifecycle rather than a stand-in for it.
## Staging `vendor/`
Both probes import from `./vendor/`, which is not checked in. Populate it from a
build:
```sh
mkdir -p vendor
cp -r ../../build vendor/quantus-crypto
cp -r ../../../wasm-util/build vendor/wasm-util
sed -i "s|from '@polkadot/wasm-util/base64'|from '../wasm-util/base64.js'|; \
s|from '@polkadot/wasm-util/fflate'|from '../wasm-util/fflate.js'|" \
vendor/quantus-crypto/init.js
```
The rewrite is needed because a browser cannot resolve bare specifiers. A real
extension build does this with a bundler; here it is one `sed` rather than a
build step, because the probe exists to test the WASM, not the bundler.
@@ -0,0 +1,5 @@
<!doctype html><meta charset="utf-8"><title>quantus-crypto worker probe</title>
<body style="font:13px ui-monospace,monospace;padding:16px;background:#111;color:#ddd">
<h3 style="font:600 14px system-ui">@quantus/crypto in a module Worker under the extension CSP</h3>
<pre id="out">running…</pre>
<script type="module" src="./main.js"></script>
@@ -0,0 +1,3 @@
const w = new Worker('./worker.js', { type: 'module' });
w.onmessage = (e) => { document.getElementById('out').textContent = e.data; };
w.onerror = (e) => { document.getElementById('out').textContent = 'worker error: ' + (e.message || 'see console'); };
@@ -0,0 +1,11 @@
{
"manifest_version": 3,
"name": "quantus-crypto MV3 probe",
"version": "0.0.1",
"description": "Loads @quantus/crypto in an MV3 service worker and signs once.",
"background": { "service_worker": "sw.js", "type": "module" },
"action": { "default_title": "probe", "default_popup": "popup.html" },
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}
}
@@ -0,0 +1,4 @@
<!doctype html><meta charset="utf-8"><title>quantus-crypto probe</title>
<body style="font:13px system-ui;padding:12px;min-width:380px">
<pre id="out">running…</pre>
<script type="module" src="popup.js"></script>
@@ -0,0 +1,4 @@
const out = document.getElementById('out');
chrome.runtime.sendMessage({ probe: true }, (r) => {
out.textContent = r ? r.text : `no response: ${chrome.runtime.lastError?.message}`;
});
@@ -0,0 +1,15 @@
import http from 'node:http';
import fs from 'node:fs';
import path from 'node:path';
const types = { '.html': 'text/html', '.js': 'text/javascript', '.json': 'application/json', '.wasm': 'application/wasm' };
http.createServer((req, res) => {
const p = path.join(process.cwd(), decodeURIComponent(req.url.split('?')[0]));
const f = fs.existsSync(p) && fs.statSync(p).isDirectory() ? path.join(p, 'index.html') : p;
if (!fs.existsSync(f)) { res.writeHead(404); return res.end('nope'); }
res.writeHead(200, {
'Content-Type': types[path.extname(f)] || 'application/octet-stream',
// exactly the extension_pages CSP from both manifests
'Content-Security-Policy': "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
});
fs.createReadStream(f).pipe(res);
}).listen(8731, () => console.log('probe on http://127.0.0.1:8731'));
+52
View File
@@ -0,0 +1,52 @@
// MV3 service worker. Imports @quantus/crypto at module scope — i.e. on every
// cold start, which is what actually needs proving: the worker is killed between
// messages and must re-instantiate a 146 KB inlined base64 wasm each time.
import {
accountFromPublicKey, contextForSpec, initWasm, keypairFromSeed,
Scheme, sign, verify
} from './vendor/quantus-crypto/index.js';
const hex = (u8) => [...u8].map((b) => b.toString(16).padStart(2, '0')).join('');
function probe () {
const lines = [];
const t0 = performance.now();
const err = initWasm();
const tInit = performance.now() - t0;
lines.push(`context: ${typeof window === 'undefined' ? 'service worker (no DOM)' : 'page'}`);
lines.push(`initWasm: ${err === null ? 'ok' : 'FAILED — ' + err} (${tInit.toFixed(1)} ms)`);
if (err) return lines.join('\n');
const t1 = performance.now();
const pair = keypairFromSeed(new Uint8Array(32), Scheme.MlDsa87);
const tKeygen = performance.now() - t1;
const account = hex(accountFromPublicKey(pair.publicKey));
const expected = '1883df2ae47d1fd428a6b8237ad7b59cf0facccaacac4541ef7758be44b3c333';
lines.push(`keygen: ${tKeygen.toFixed(1)} ms`);
lines.push(`account: ${account.slice(0, 24)}${account === expected ? 'matches quantus-cli' : 'MISMATCH'}`);
const msg = new TextEncoder().encode('extrinsic payload');
const ctx = contextForSpec(148);
const t2 = performance.now();
const sig = sign(msg, pair, ctx, Scheme.MlDsa87);
const tSign = performance.now() - t2;
lines.push(`sign: ${tSign.toFixed(1)} ms (${sig.length} bytes)`);
lines.push(`verify: ${verify(msg, sig, pair.publicKey, ctx, Scheme.MlDsa87) ? 'ok' : 'FAILED'}`);
lines.push(`ctx sep: ${verify(msg, sig, pair.publicKey, contextForSpec(147), Scheme.MlDsa87) ? 'FAILED (verified under wrong ctx)' : 'ok (rejected under spec 147 ctx)'}`);
return lines.join('\n');
}
chrome.runtime.onMessage.addListener((_m, _s, respond) => {
try {
respond({ text: probe() });
} catch (e) {
respond({ text: `threw: ${e && e.message ? e.message : e}\n${e && e.stack ? e.stack : ''}` });
}
return true;
});
@@ -0,0 +1,32 @@
// A module Worker: no DOM, no window, same CSP as the extension pages.
import { accountFromPublicKey, contextForSpec, initWasm, keypairFromSeed, Scheme, sign, verify } from './vendor/quantus-crypto/index.js';
const hex = (u8) => [...u8].map((b) => b.toString(16).padStart(2, '0')).join('');
const lines = [];
const t0 = performance.now();
const err = initWasm();
const tInit = performance.now() - t0;
lines.push(`hasDOM: ${typeof document !== 'undefined'} hasWindow: ${typeof window !== 'undefined'}`);
lines.push(`initWasm: ${err === null ? 'ok' : 'FAILED - ' + err} (${tInit.toFixed(1)} ms cold)`);
if (err === null) {
const t1 = performance.now();
const pair = keypairFromSeed(new Uint8Array(32), Scheme.MlDsa87);
const tKeygen = performance.now() - t1;
const account = hex(accountFromPublicKey(pair.publicKey));
lines.push(`keygen: ${tKeygen.toFixed(1)} ms`);
lines.push(`account: ${account === '1883df2ae47d1fd428a6b8237ad7b59cf0facccaacac4541ef7758be44b3c333' ? 'matches quantus-cli' : 'MISMATCH ' + account}`);
const msg = new TextEncoder().encode('extrinsic payload');
const ctx = contextForSpec(148);
const t2 = performance.now();
const sig = sign(msg, pair, ctx, Scheme.MlDsa87);
const tSign = performance.now() - t2;
const t3 = performance.now();
const ok = verify(msg, sig, pair.publicKey, ctx, Scheme.MlDsa87);
const tVerify = performance.now() - t3;
lines.push(`sign: ${tSign.toFixed(1)} ms (${sig.length} bytes)`);
lines.push(`verify: ${tVerify.toFixed(1)} ms ${ok ? 'ok' : 'FAILED'}`);
lines.push(`ctx sep: ${verify(msg, sig, pair.publicKey, contextForSpec(147), Scheme.MlDsa87) ? 'FAILED' : 'ok (rejected under spec-147 ctx)'}`);
}
postMessage(lines.join('\n'));
@@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "..",
"composite": false,
"declaration": true,
"outDir": "./build",
"rootDir": "./src",
"emitDeclarationOnly": false
},
"exclude": [
"**/*.spec.ts"
],
"include": [
"src/**/*.ts"
],
"references": []
}
+7 -7
View File
@@ -14,19 +14,19 @@
"url": "https://github.com/polkadot-js/wasm.git"
},
"sideEffects": [
"./detectPackage.js",
"./detectPackage.cjs"
"./packageDetect.js",
"./packageDetect.cjs"
],
"type": "module",
"version": "7.3.2",
"version": "7.5.4",
"main": "index.js",
"dependencies": {
"@polkadot/wasm-util": "7.3.2",
"tslib": "^2.6.2"
"@polkadot/wasm-util": "7.5.4",
"tslib": "^2.7.0"
},
"devDependencies": {
"@polkadot/util": "^12.6.1",
"@polkadot/x-randomvalues": "^12.6.1"
"@polkadot/util": "^14.0.1",
"@polkadot/x-randomvalues": "^14.0.1"
},
"peerDependencies": {
"@polkadot/util": "*",
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-bridge authors & contributors
// Copyright 2019-2026 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
// A number of functions are "unsafe" and purposefully so - it is
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-bridge authors & contributors
// Copyright 2019-2026 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './bridge.js';
-4
View File
@@ -1,4 +0,0 @@
// Copyright 2017-2023 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
export default [];
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2019-2023 @polkadot/wasm-bridge authors & contributors
// Copyright 2019-2026 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './detectPackage.js';
import './packageDetect.js';
export * from './bundle.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-bridge authors & contributors
// Copyright 2019-2026 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { InitFn, InitPromise, InitResult, WasmBaseInstance, WasmImports } from './types.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-bridge authors & contributors
// Copyright 2019-2026 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './index.js';
@@ -1,11 +1,11 @@
// Copyright 2017-2023 @polkadot/wasm-bridge authors & contributors
// Copyright 2017-2026 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
// (packageInfo imports will be kept as-is, user-editable)
import { detectPackage } from '@polkadot/util';
import others from './detectOther.js';
import { packageInfo } from './packageInfo.js';
detectPackage(packageInfo, null, others);
detectPackage(packageInfo, null, []);
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2017-2023 @polkadot/wasm-bridge authors & contributors
// Copyright 2017-2026 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
export const packageInfo = { name: '@polkadot/wasm-bridge', path: 'auto', type: 'auto', version: '7.3.2' };
export const packageInfo = { name: '@polkadot/wasm-bridge', path: 'auto', type: 'auto', version: '7.5.4' };
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-bridge authors & contributors
// Copyright 2019-2026 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Use non-strong types instead of WasmImports which may not
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-bridge authors & contributors
// Copyright 2019-2026 @polkadot/wasm-bridge authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BridgeBase, WasmBaseInstance } from './types.js';
+7 -4
View File
@@ -13,15 +13,18 @@
"type": "git",
"url": "https://github.com/polkadot-js/wasm.git"
},
"sideEffects": false,
"sideEffects": [
"./packageDetect.js",
"./packageDetect.cjs"
],
"type": "module",
"version": "7.3.2",
"version": "7.5.4",
"main": "index.js",
"dependencies": {
"tslib": "^2.6.2"
"tslib": "^2.7.0"
},
"devDependencies": {
"@polkadot/util": "^12.6.1"
"@polkadot/util": "^14.0.1"
},
"peerDependencies": {
"@polkadot/util": "*"
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { asmJsInit } from './cjs/data.js';
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
const data = require('../data.js');
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
export declare const asmJsInit: null;
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
const asmJsInit = null;
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
export declare const asmJsInit: null;
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
export const asmJsInit = null;
@@ -1,4 +0,0 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
export default [];
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './detectPackage.js';
import './packageDetect.js';
export * from './bundle.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './index.js';
@@ -1,11 +1,11 @@
// Copyright 2017-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// Copyright 2017-2026 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
// (packageInfo imports will be kept as-is, user-editable)
import { detectPackage } from '@polkadot/util';
import others from './detectOther.js';
import { packageInfo } from './packageInfo.js';
detectPackage(packageInfo, null, others);
detectPackage(packageInfo, null, []);
@@ -1,6 +1,6 @@
// Copyright 2017-2023 @polkadot/wasm-crypto-asmjs authors & contributors
// Copyright 2017-2026 @polkadot/wasm-crypto-asmjs authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
export const packageInfo = { name: '@polkadot/wasm-crypto-asmjs', path: 'auto', type: 'auto', version: '7.3.2' };
export const packageInfo = { name: '@polkadot/wasm-crypto-asmjs', path: 'auto', type: 'auto', version: '7.5.4' };
+10 -10
View File
@@ -14,24 +14,24 @@
"url": "https://github.com/polkadot-js/wasm.git"
},
"sideEffects": [
"./detectPackage.js",
"./detectPackage.cjs"
"./packageDetect.js",
"./packageDetect.cjs"
],
"type": "module",
"version": "7.3.2",
"version": "7.5.4",
"browser": "wasm.js",
"main": "wasm.js",
"react-native": "asm.js",
"dependencies": {
"@polkadot/wasm-bridge": "7.3.2",
"@polkadot/wasm-crypto-asmjs": "7.3.2",
"@polkadot/wasm-crypto-wasm": "7.3.2",
"@polkadot/wasm-util": "7.3.2",
"tslib": "^2.6.2"
"@polkadot/wasm-bridge": "7.5.4",
"@polkadot/wasm-crypto-asmjs": "7.5.4",
"@polkadot/wasm-crypto-wasm": "7.5.4",
"@polkadot/wasm-util": "7.5.4",
"tslib": "^2.7.0"
},
"devDependencies": {
"@polkadot/util": "^12.6.1",
"@polkadot/x-randomvalues": "^12.6.1"
"@polkadot/util": "^14.0.1",
"@polkadot/x-randomvalues": "^14.0.1"
},
"peerDependencies": {
"@polkadot/util": "*",
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-init authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { InitFn } from '@polkadot/wasm-bridge/types';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-init authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { InitFn } from '@polkadot/wasm-bridge/types';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-init authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './wasm.js';
@@ -1,8 +0,0 @@
// Copyright 2017-2023 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { packageInfo as bridgeInfo } from '@polkadot/wasm-bridge/packageInfo';
import { packageInfo as asmInfo } from '@polkadot/wasm-crypto-asmjs/packageInfo';
import { packageInfo as wasmInfo } from '@polkadot/wasm-crypto-wasm/packageInfo';
export default [bridgeInfo, asmInfo, wasmInfo];
@@ -1,11 +0,0 @@
// Copyright 2017-2023 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
import { detectPackage } from '@polkadot/util';
import others from './detectOther.js';
import { packageInfo } from './packageInfo.js';
detectPackage(packageInfo, null, others);
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-init authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './wasm.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-init authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { InitFn } from '@polkadot/wasm-bridge/types';
@@ -0,0 +1,14 @@
// Copyright 2017-2026 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
// (packageInfo imports will be kept as-is, user-editable)
import { detectPackage } from '@polkadot/util';
import { packageInfo as bridgeInfo } from '@polkadot/wasm-bridge/packageInfo';
import { packageInfo as asmInfo } from '@polkadot/wasm-crypto-asmjs/packageInfo';
import { packageInfo as wasmInfo } from '@polkadot/wasm-crypto-wasm/packageInfo';
import { packageInfo } from './packageInfo.js';
detectPackage(packageInfo, null, [asmInfo, bridgeInfo, wasmInfo]);
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2017-2023 @polkadot/wasm-crypto-init authors & contributors
// Copyright 2017-2026 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
export const packageInfo = { name: '@polkadot/wasm-crypto-init', path: 'auto', type: 'auto', version: '7.3.2' };
export const packageInfo = { name: '@polkadot/wasm-crypto-init', path: 'auto', type: 'auto', version: '7.5.4' };
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-init authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { WasmBaseInstance } from '@polkadot/wasm-bridge/types';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-init authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-init authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { InitFn } from '@polkadot/wasm-bridge/types';
+8 -5
View File
@@ -13,16 +13,19 @@
"type": "git",
"url": "https://github.com/polkadot-js/wasm.git"
},
"sideEffects": false,
"sideEffects": [
"./packageDetect.js",
"./packageDetect.cjs"
],
"type": "module",
"version": "7.3.2",
"version": "7.5.4",
"main": "index.js",
"dependencies": {
"@polkadot/wasm-util": "7.3.2",
"tslib": "^2.6.2"
"@polkadot/wasm-util": "7.5.4",
"tslib": "^2.7.0"
},
"devDependencies": {
"@polkadot/util": "^12.6.1"
"@polkadot/util": "^14.0.1"
},
"peerDependencies": {
"@polkadot/util": "*"
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { base64Decode, unzlibSync } from '@polkadot/wasm-util';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
export declare const bytes: string;
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
exports.lenIn = 0;
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
const bytes = require('../bytes.js');
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
export declare const bytes: string;
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
export const bytes = '';
@@ -1,4 +0,0 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
export default [];
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './detectPackage.js';
import './packageDetect.js';
export * from './bundle.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './index.js';
@@ -1,11 +1,11 @@
// Copyright 2017-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2017-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
// (packageInfo imports will be kept as-is, user-editable)
import { detectPackage } from '@polkadot/util';
import others from './detectOther.js';
import { packageInfo } from './packageInfo.js';
detectPackage(packageInfo, null, others);
detectPackage(packageInfo, null, []);
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2017-2023 @polkadot/wasm-crypto-wasm authors & contributors
// Copyright 2017-2026 @polkadot/wasm-crypto-wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
export const packageInfo = { name: '@polkadot/wasm-crypto-wasm', path: 'auto', type: 'auto', version: '7.3.2' };
export const packageInfo = { name: '@polkadot/wasm-crypto-wasm', path: 'auto', type: 'auto', version: '7.5.4' };
+1 -1
View File
@@ -1,2 +1,2 @@
[dependencies]
std = { default-features = false, features = ["panic_immediate_abort"] }
std = { default-features = false, features = ["panic_immediate_abort"] }
+12 -12
View File
@@ -14,8 +14,6 @@
"url": "https://github.com/polkadot-js/wasm.git"
},
"sideEffects": [
"./detectPackage.js",
"./detectPackage.cjs",
"./initNone.js",
"./initNone.cjs",
"./initOnlyAsm.js",
@@ -23,22 +21,24 @@
"./initOnlyWasm.js",
"./initOnlyWasm.cjs",
"./initWasmAsm.js",
"./initWasmAsm.cjs"
"./initWasmAsm.cjs",
"./packageDetect.js",
"./packageDetect.cjs"
],
"type": "module",
"version": "7.3.2",
"version": "7.5.4",
"main": "index.js",
"dependencies": {
"@polkadot/wasm-bridge": "7.3.2",
"@polkadot/wasm-crypto-asmjs": "7.3.2",
"@polkadot/wasm-crypto-init": "7.3.2",
"@polkadot/wasm-crypto-wasm": "7.3.2",
"@polkadot/wasm-util": "7.3.2",
"tslib": "^2.6.2"
"@polkadot/wasm-bridge": "7.5.4",
"@polkadot/wasm-crypto-asmjs": "7.5.4",
"@polkadot/wasm-crypto-init": "7.5.4",
"@polkadot/wasm-crypto-wasm": "7.5.4",
"@polkadot/wasm-util": "7.5.4",
"tslib": "^2.7.0"
},
"devDependencies": {
"@polkadot/util": "^12.6.1",
"@polkadot/x-randomvalues": "^12.6.1"
"@polkadot/util": "^14.0.1",
"@polkadot/x-randomvalues": "^14.0.1"
},
"peerDependencies": {
"@polkadot/util": "*",
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { WasmCryptoInstance } from '@polkadot/wasm-crypto-init/types';
-11
View File
@@ -1,11 +0,0 @@
// Copyright 2017-2023 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
import { detectPackage } from '@polkadot/util';
import others from './detectOther.js';
import { packageInfo } from './packageInfo.js';
detectPackage(packageInfo, null, others);
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2019-2023 @polkadot/wasm-crypto authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './detectPackage.js';
import './packageDetect.js';
export * from './bundle.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2023 @polkadot/wasm-crypto authors & contributors
// Copyright 2019-2026 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { InitFn } from '@polkadot/wasm-bridge/types';

Some files were not shown because too many files have changed in this diff Show More