Compare commits

...
Author SHA1 Message Date
grenadeandClaude Opus 5 7f23d0a609 fix: gate quantum-safe chains in the wallet, not in the network list
Filtering `filterAvailable` by `isQuantumSafe` removed Polkadot and Kusama from
`selectableNetworks`, and `@polkadot/types-known` throws for a chain it holds
upgrade history for but cannot find:

    Error: Unable to find info for chain kusama
      at mapRaw (@polkadot/types-known/upgrades/index.js:13:15)

which made `@polkadot/api` fail at *import* — nothing to do with Quantus, and
not diagnosable from the message.

The mistake was gating a library primitive. These lists say what Substrate
knows about; what a wallet offers is the wallet's decision, and `isQuantumSafe`
stays exported so the extension can make it in its own chain list. Keeping them
complete also means a pasted Polkadot address still decodes and renders as what
it is — recognisable, and therefore refusable. A wallet that cannot decode an
address it will not accept can only say "invalid", which reads as a typo rather
than a warning.

`index.spec.ts` is restored from upstream with only the testnet-exposure and
shared-prefix deltas re-applied, since its network-ordering assertions had
started passing vacuously once Polkadot and Kusama left the list.

Also corrects Heisenberg's token symbol to HEI; `system_properties` on
wss://a1-heisenberg.quantus.cat says HEI, not QTC.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-15 14:14:58 +03:00
rob thijssenandClaude Opus 5 49f3442e5c build: version the forked packages 14.0.3-quantus.1, not upstream's 14.0.3
Publishing under upstream's exact version was not merely confusing in a lockfile
— it was unsafe.

@polkadot/util-crypto pins @polkadot/networks at an exact version, and
@polkadot/keyring pins util-crypto the same way. With every fork numbered
14.0.3, a consumer that forgot one resolution would have yarn quietly satisfy
that pin from npm: the *unforked* upstream package, installing cleanly, with no
Quantus network entry or no isDilithium, and nothing to indicate anything was
wrong until something failed much later for an unrelated-looking reason.

Renaming the forks makes that a resolution failure instead. Verified by deleting
the @polkadot/networks resolution in quantus/ui and running an install:

  YN0082: @polkadot/networks@npm:14.0.3-quantus.1: No candidates found

Unforked dependencies keep upstream's numbers — @polkadot/util, x-bigint,
x-randomvalues, wasm-* — because they come from npm and should.

Published with `--tag quantus`, which npm requires for a prerelease version and
which is the honest dist-tag anyway: these are not upstream's `latest`. The tag
is informational, since consumers pin by tarball URL. The old 14.0.3 artifacts
have been deleted from the registry so nothing can resolve to them.

Refs 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:27:59 +03:00
rob thijssenandClaude Opus 5 b39241c840 build: take @quantus/crypto from the Gitea registry, not a symlink
Replaces the `link:../wasm/packages/quantus-crypto/build` resolution with an
npmScopes entry routing @quantus to https://git.lair.cafe/api/packages/quantus/npm/.

Routing the whole scope is safe here because we own every name in it. The forked
@polkadot packages cannot be handled the same way — most of that scope is
unforked and lives on npm — which is why downstream repos pin those to tarball
URLs instead.

The symlink approach had run out of road. node resolves a portal or link to its
realpath, so a consumer's transitive dependencies then resolve inside the *linked
package's* tree, where workspace entries point at source directories with no
exports map. That cost one workaround here and four in quantus/ui.

Reads from the registry are anonymous; publishing uses
`pass gitea/package-publisher-token`. Note that a version cannot be overwritten
in place — npm refuses — so republishing is delete-then-publish, which that token
can also do.

Refs 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:08:27 +03:00
rob thijssenandClaude Opus 5 74e1e0cc6a feat(util-crypto): export quantus-cli wallet files
The counterpart to the importer, and the thing that stops it being a one-way
door. Without it an extension backup is readable only by polkadot-js-format
tooling, so a user who brought a wallet in could not take it back out.

The assumption that blocked this — that we cannot write the CLI format because we
do not store a mnemonic — turns out to be false. The format has a null-mnemonic
shape and the CLI writes one itself for every raw-seed wallet; crystal_alice,
which `quantus developer create-test-wallets` produces, has exactly that
alongside `derivation_path: "m/"`.

Verified against quantus-cli 2.2.2 rather than inferred. Exports of both schemes
were written into ~/.quantus/wallets and the CLI listed them with the correct
address, scheme and human checkphrase, showed them under `wallet view`, and
answered `export --format mnemonic` with "Mnemonic phrase is not available for
this wallet" — which is a correct statement about the file, and only reachable by
deriving the Argon2 key, opening the AES-GCM box and parsing the payload. Those
probes were removed; the committed test round-trips through our own importer,
since CI has no CLI.

What a null-mnemonic export costs the user is exactly one thing, and it is not
spending: the CLI cannot re-export a phrase from it or derive further accounts.
That is the honest consequence of never storing a phrase we showed once, and it
is the same position a raw-seed CLI wallet is already in.

Argon2 parameters match the CLI's rather than being raised. They travel in the
file and the CLI reads them, so in principle anything works — but "in principle"
is load-bearing there, and a wallet a slightly older CLI cannot open is worse
than a conservatively tuned KDF. Salt and nonce are fresh per export.

metadata.exported_by marks the file, so a CLI maintainer reading a bug report can
tell where it came from without inferring from shape.

Refs quantus/common#8

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-15 10:33:20 +03:00
rob thijssenandClaude Opus 5 e55352aa4e feat(util-crypto): import quantus-cli wallet files
A must-have per the UX decision in quantus/extension#8: the extension has to be
able to take a wallet a user already has. My earlier recommendation against this
was withdrawn — the reasoning was that a file importer would pull the mnemonic
into storage, which is a property of an implementation rather than of the format.
This one derives and discards.

Converts at the edge rather than teaching the keyring a second container.
Decrypt (Argon2id -> AES-256-GCM), take the key material, and hand back a plain
keypair for the caller to re-encrypt as the extension's own PKCS8 under a
password the user chooses. decodePair does not learn about Argon2 and the keyring
keeps exactly one on-disk format.

The CLI stores a mnemonic in every HD wallet. This returns `hadMnemonic` and not
the phrase — the posture is that a storage password unlocks signing and nothing
on disk regenerates the tree, and an import must leave us holding exactly what
importing the same mnemonic by hand would leave us holding. Matching upstream,
which stores key material only.

No new dependencies: @noble/hashes/argon2 was already a util-crypto dependency
and AES-GCM is WebCrypto. Async only because WebCrypto has no synchronous form,
which is a second reason this belongs at the edge — createFromJson is sync and
should stay so.

Three things are refused rather than worked around:

Argon2 parameters are read from the file, not assumed. They are stored because
they are expected to change, and a build that hardcoded m=19456,t=2,p=1 would
reject a correct password on an older or newer wallet — the least useful thing it
could say.

Non-empty kyber_ciphertext/kyber_public_key, and any encryption_version other
than 2, are refused with a message. Those fields are an ML-KEM envelope mode that
is planned or optional; decrypting by the path we know and ignoring them would
fail later and more confusingly, most likely as a bad-password error.

The address sits outside the encrypted blob, so it is the one field an attacker
can edit without the password. cliWalletAddressMatches is exposed separately so
a caller can say "this file's address does not match its key" rather than
"wrong password" — a user can act on the difference.

Fixtures are genuine CLI 2.2.2 output, not blobs this repo encrypted; testing a
decoder against its own encryption proves only self-consistency. They contain no
secret: the mnemonic inside each is the public Substrate dev phrase and the
password is empty, which is the only reason committing a decryptable wallet is
acceptable. The strongest test reaches the same account two ways — through the
CLI container and by deriving from that phrase ourselves.

Refs quantus/common#7

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-15 09:44:07 +03:00
rob thijssenandClaude Opus 5 f7dc72e32c feat(keyring,util-crypto): BIP44 derivation for ML-DSA, in place of junctions
createFromUri now derives ML-DSA pairs from the mnemonic along a hardened BIP44
path rather than from a seed along a junction chain. Lattice keys have no public
derivability, so there is no soft junction to emulate and the chain's own
Pair::derive refuses for the same reason.

The path is m/44'/189189'/<account>'/0'/<0 for 87 | 1 for 65>'. Two things about
it are unusual and both are deliberate: the account index sits at the third level
rather than the last, and the trailing index carries the scheme rather than an
address index. It is what quantus-cli and the mobile wallet already derive, so
anything else produces addresses no other Quantus tool can find.

The ML-DSA branch sits before the existing seeding, not after, because that
seeding is the wrong one for us. mnemonicToMiniSecret is Substrate's variant and
the default reach in this file; using it here yields a perfectly well-formed key
for an account nobody owns. We need the 64-byte BIP39 seed and the HMAC-SHA512
chain keyed with "Dilithium seed", which is what the hdwallet crate does.

Accepted suri forms are nothing (account 0, matching quantus-cli's default),
//<n> for an account index, and //m/44'/… for a full path. Everything else
throws. That matters more than it looks: the syntax was built for curve
junctions, where /foo is soft and //foo hard over arbitrary bytes, and quietly
reinterpreting //Alice as something would hand back an address no other tool
derives and no seed phrase obviously recovers.

A raw hex seed is taken undederived — that is how the dev-genesis accounts are
defined — and combining one with a derivation path is refused rather than
guessed, since whether such a seed is the master or already derived is
unanswerable.

pair.derive() refuses with its own message. Not "not implemented" but not
possible: a child would have to come from this pair's key material, and ML-DSA
keys are not derivable from one another at all. The caller needs the mnemonic, so
the error says so rather than being generically unhelpful.

Verified against quantus-cli for both schemes from the public Substrate dev
phrase, plus index separation, explicit paths agreeing with indices, passphrase
sensitivity, and every refusal.

Refs quantus/common#4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-15 08:56:20 +03:00
rob thijssenandClaude Opus 5 506b77351c fix(keyring): make account backup and restore work for ML-DSA pairs
Before this, an exported Quantus account could not be imported by anything.
decodePair located its divider by trying the two secret lengths every curve
scheme uses, 64 then 32; an ML-DSA secret is 4032 or 4896 bytes, so neither
matched and restore threw "Invalid encoding divider found in body". That is a
data-durability bug rather than a convenience one, which is why it lands before
any build lets a user create an account.

decodePair now takes an optional secretLength. The caller passes it rather than
this function searching for the divider: searching would work almost always and
fail catastrophically when it did not, since PAIR_DIV is five bytes and a
4032-byte secret contains a false match about once in 270 million keys — and the
result would be a silently wrong key rather than an error. The public key is read
as the remainder, because its length also varies and the body ends there.

The harder half is ordering. createFromJson returns a *locked* pair and callers
read pair.address off it long before any password appears. Every other scheme
manages because the address is the public key; an ML-DSA account id is a one-way
Poseidon2 hash and the public key is inside the encrypted blob. So PairInfo gains
an optional accountId, carried as data and used for the address while locked,
which decodePkcs8 clears once the real key arrives.

It also checks them against each other, and that check is not paranoia. For the
curve schemes a JSON file with an edited `address` field cannot decode at all.
Here it decodes perfectly and yields a pair reporting an address its key does not
control — a user would see someone else's address in their own wallet and believe
they held it. Pinned by a test that tampers exactly that field.

One more silent-wrong-key path closed: decodePkcs8 decided "secret key or seed?"
by length, so a 4032-byte ML-DSA secret took the seed branch and was fed to keygen
as entropy, producing a valid and entirely wrong key. The type knows the answer,
so it is asked.

Refs quantus/common#3

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-15 08:50:56 +03:00
rob thijssenandClaude Opus 5 2bffbc62bc feat(keyring,util-crypto): ML-DSA arms for createPair, alongside the curve ones
Wires @quantus/crypto into the four TYPE_* maps, so a keyring pair of type
dilithium65/dilithium87 can be created, addressed, signed with and verified.
The curve arms are untouched: quantus/common#6 removes them, but only once
post-quantum signing is proven end to end, so that a rejected first extrinsic has
two suspects rather than three.

util-crypto gains thin dilithium/ wrappers so keyring composes primitives the way
it already does for ed25519, rather than reaching into @quantus/crypto directly.

TYPE_ADDRESS is the interesting arm. Every other entry is identity or a cheap
re-encoding because on Substrate the address *is* the public key; ML-DSA takes
1952 or 2592 bytes to 32 with no way back. dilithiumVerify therefore mirrors the
runtime's Verify::verify rather than inventing anything: split sig||pk, hash the
embedded public key, check it equals the account being verified against, and only
then check the signature. Skipping the account check would accept a valid
signature from *some* key rather than *the* key, which is the whole property.

TYPE_PREFIX gets 0 for dilithium87 and 1 for dilithium65. For the curve types
those index Substrate's MultiSignature; here they index the runtime's
DilithiumSignatureScheme — a different enum reached by the same withType
mechanism, so nothing upstream of createPair needs to know.

Signing takes a mandatory context, which is quantus/common#5 arriving early
because the two are inseparable: TYPE_SIGNATURE cannot have an ML-DSA arm without
one. There is deliberately no default. Extrinsics on spec >= 148 verify under
QUANTUS_EXTRINSIC and earlier ones under the empty context, and the wrong choice
is a valid signature the chain rejects, indistinguishable locally. Only the
caller knows the spec version.

The curve sign functions are wrapped rather than passed by reference: ed25519Sign
and sr25519Sign take a third argument of their own (onlyJs), and letting it line
up with the context slot would silently reinterpret a Uint8Array as a boolean.

VRF signing refuses for ML-DSA. The construction fakes a VRF from an ordinary
signature, which is sound only given properties ML-DSA's signature does not have;
producing something that looks like a VRF proof and cannot be verified as one is
worse than refusing.

Two runtime whitelists sat behind the KeypairType union and silently rejected the
new types — the Keyring constructor and createFromJson. The compiler cannot see
those, and the first failed at construction with a message listing four types.
Both now use one named KEYPAIR_TYPES so the next change to the union has a single
place to look and the message cannot drift from the check.

Verified against quantus-cli, not against ourselves: keyring.addFromSeed with 32
zero bytes yields crystal_alice's address, signs to the runtime's wire length,
verifies under the extrinsic context and fails under the spec-147 one, and
another account's signature is rejected for it.

Refs quantus/common#2, quantus/common#5

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-10 18:43:25 +03:00
rob thijssenandClaude Opus 5 c616a0d949 feat(networks): offer only chains whose accounts are post-quantum
Deployments built on this fork should gate on chains using PQ algorithms, so
`availableNetworks` and `selectableNetworks` now exclude anything whose
standardAccount is a discrete-log scheme. *25519, Ed25519, Sr25519 and secp256k1
are all broken at once by Shor's algorithm, so a chain using one has no
quantum-safe account type to offer whatever else is true of it. A null
standardAccount counts as unsafe: unknown is not the same as safe, and being
wrong in that direction means a user holding funds under a recoverable key.

`allNetworks` is deliberately left complete. Gating what we offer must not gate
what we can read — a pasted Polkadot address has to render as a Polkadot address
so it can be recognised and refused. A wallet that cannot decode an address it
will not accept can only say "invalid", which reads as a typo rather than as a
warning.

Five upstream tests asserted the shape of a list that now holds two entries
instead of a hundred and fifty. Each is rewritten to assert the rule that
displaced it rather than deleted:
  - starting order 0/2/42 and tail 'ZERO'/'Zeitgeist' become "offers only
    post-quantum chains", plus an explicit test that non-PQ chains remain
    decodable, since that distinction is the whole design
  - the ledger slip44 check becomes its inverse: nothing offered should claim
    hardware support, because no PQ chain has a Ledger app — the devices speak
    ed25519 over the Substrate app. knownLedger is left intact rather than
    emptied so a rebase has nothing to reconcile; it simply matches nothing
  - the Polkadot field fixture becomes the Quantus one

Refs quantus/common#1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-10 18:28:13 +03:00
rob thijssenandClaude Opus 5 6ed8b1ea6d feat(util-crypto,networks): open KeypairType to ML-DSA, register Quantus
Adds dilithium65 and dilithium87 to KeypairType, and Quantus to the network
registry at SS58 prefix 189.

Two types rather than one because the parameter set is not an implementation
detail: it selects the signature enum variant byte the chain reads off the wire,
it selects the trailing hardened index of the derivation path, and it fixes the
key and signature lengths. New accounts use dilithium65; dilithium87 is legacy,
used by accounts predating the recorded scheme and by the dev-genesis accounts,
so it is supported but never chosen.

Opening the union turns every table keyed by KeypairType into a type error,
which is the point — it enumerates precisely the places needing a post-quantum
answer. Where that answer is not written yet, assertSubstrateType() expresses the
gap as a named, typed refusal at the call site rather than a silent undefined
from an object index. It throws rather than degrading because all of these paths
produce key material or an address, and a wrong-but-plausible value means funds
at an address nobody controls. Call sites disappear as quantus/common#2 and #4
land. keyFromPath throws for the same reason: silently ignoring a derivation
path would hand back the parent key under a child's name.

Quantus is appended to the registry rather than merged — @substrate/ss58-registry
does not carry it and nothing occupies prefixes 185..195, so there is nothing to
override, and appending means a registry bump cannot silently drop it.
standardAccount is 'ml-dsa', a value upstream's closed union does not allow,
hence the widened KnownSubstrate; it is load-bearing, since a missing
standardAccount makes toExpanded mark the network isIgnored.

Testnets are first-class in this fork. Upstream drops any testnet from
availableNetworks however complete its entry is, which suits a production chain
picker and not us: Quantus is under active development and a wallet that cannot
reach Heisenberg can only be tested against real funds. The exception is an
allowlist (exposedTestnet) rather than removing the rule — removing it exposed
all fifteen of upstream's testnets too, moving the sorted tail from 'ZERO' to
'ZERO Alphaville', which is a change we have no reason to make. isTestnet is
still reported honestly so consumers can label or filter; we just stop deciding
for them.

Genesis hashes come from blackbeard.observer's config, which records their
provenance: mainnet read from the node with chain_getBlockHash(0) on 2026-09-09,
its launch day, and explicitly not the staging chain that telemetry calls
"Quantus Staging Mainnet". Symbol and decimals (QTC, 12) agree between the
chain's chain_spec.rs and the Dart SDK's AppConstants.

Three upstream tests encoded rules this fork no longer follows. Each is rewritten
to assert the new rule rather than deleted:
  - genesis entries for testnets are now allowed, for ones we opted into
  - an exposed testnet must still be labelled a testnet, since a user cannot tell
    play money from real money by looking at an address
  - one network per SS58 prefix stops holding when a chain has a testnet;
    mainnet and Heisenberg are both 189 because an address is valid on either,
    and consumers key on genesisHash rather than prefix

Refs quantus/common#1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-10 14:50:49 +03:00
github-actions[bot] 9281daa14c [CI Skip] release/stable 14.0.3
Lock Threads / lock (push) Has been cancelled
skip-checks: true
2026-03-23 12:53:26 +00:00
Tarik Gul c7ed69fe55 14.0.3 (#2025) 2026-03-23 14:48:13 +02:00
github-actions[bot] 22a1246672 [CI Skip] bump/beta 14.0.3-0-x
skip-checks: true
2026-03-22 21:42:26 +00:00
Francis O'Brien 4a5b6acc78 Fix(hw-ledger): reset transport on operation errors and add explicit disconnect API (#2024)
* fix(hw-ledger): close transport on withApp errors

* feat(hw-ledger): add disconnect method to close active transport connection
2026-03-22 17:37:13 -04:00
github-actions[bot] 152d4609a4 [CI Skip] release/stable 14.0.2
skip-checks: true
2026-03-16 18:24:20 +00:00
Tarik Gul 4ab957e568 14.0.2 (#2023) 2026-03-16 20:19:11 +02:00
github-actions[bot] 610c8bb15f [CI Skip] bump/beta 14.0.2-1-x
skip-checks: true
2026-03-13 20:55:22 +00:00
AndreasGassmann 372d460f1a fix(util-crypto): use correct x/y getters in secp256k1Expand (#2018) 2026-03-13 16:50:22 -04:00
github-actions[bot] a8732a99aa [CI Skip] bump/beta 14.0.2-0-x
skip-checks: true
2026-03-12 16:59:11 +00:00
Tarik Gul 8d3cb0eb6b Set headers to 2026 (#2021) 2026-03-12 18:53:56 +02:00
github-actions[bot] fe0886be23 [CI Skip] release/stable 14.0.1
skip-checks: true
2025-12-09 08:34:53 +00:00
rajk93 bac357ae10 14.0.1 (#2014)
* 14.0.1

* chore: update CHANGELOG.md
2025-12-09 14:01:18 +05:30
github-actions[bot] 4ab7a5bfe0 [CI Skip] bump/beta 13.5.10-1-x
skip-checks: true
2025-12-09 07:34:50 +00:00
rajk93 440b146a24 fix(x-randomvalues): prioritize native RN modules over polyfilled crypto (#2013) 2025-12-09 13:01:07 +05:30
github-actions[bot] ae0735ccda [CI Skip] bump/beta 13.5.10-0-x
skip-checks: true
2025-12-08 13:13:55 +00:00
Paul Miller 57551b38a0 sr25519: switch from wasm to micro-sr25519 (#1971)
* sr25519: switch from wasm to micro-sr25519

* commit yarn lockfile

* Update to scure/sr25519 v0.2

* Fix build

* Commit
2025-12-08 18:40:16 +05:30
github-actions[bot] bf63a0ebf6 [CI Skip] release/stable 13.5.9
skip-checks: true
2025-11-25 08:17:11 +00:00
rajk93 d78344d000 13.5.9 (#2012) 2025-11-25 13:43:32 +05:30
github-actions[bot] 169a702c88 [CI Skip] bump/beta 13.5.9-0-x
skip-checks: true
2025-11-25 08:02:38 +00:00
rajk93 b4d73ef49a chore: bump polkadot dependencies (#2011) 2025-11-25 13:28:57 +05:30
github-actions[bot] 21ef853fbd [CI Skip] release/stable 13.5.8
skip-checks: true
2025-11-11 06:04:48 +00:00
rajk93 a4d940cb69 13.5.8 (#2010) 2025-11-11 11:31:13 +05:30
github-actions[bot] 6423537dbe [CI Skip] bump/beta 13.5.8-0-x
skip-checks: true
2025-11-11 04:05:17 +00:00
rajk93 61125b3582 chore: bump polkadot dependencies (#2009) 2025-11-11 09:31:28 +05:30
github-actions[bot] 7d77f89f34 [CI Skip] release/stable 13.5.7
skip-checks: true
2025-10-13 14:54:53 +00:00
Valentin Fernandez bee7204f95 13.5.7 (#2008) 2025-10-13 11:51:23 -03:00
github-actions[bot] bb0715ec2f [CI Skip] bump/beta 13.5.7-0-x
skip-checks: true
2025-10-13 14:24:21 +00:00
Valentin Fernandez 2212f31f9a Revert "Set pbkdf2Encode rounds to default to 210,000 (#1983)" (#2007)
This reverts commit e5e6bdd814.
2025-10-13 11:20:32 -03:00
github-actions[bot] 047840319e [CI Skip] release/stable 13.5.6
skip-checks: true
2025-08-26 16:49:24 +00:00
Arjun Porwal 3fbf0b98c2 13.5.6 (#2004) 2025-08-26 13:45:40 -03:00
github-actions[bot] e6b0411e9c [CI Skip] bump/beta 13.5.6-2-x
skip-checks: true
2025-08-26 14:00:47 +00:00
Valentin Fernandez 6e8199bab7 Bump @polkadot/wasm deps (#2002)
* bump  deps

* small tweak
2025-08-26 10:56:53 -03:00
github-actions[bot] 8ca4525570 [CI Skip] bump/beta 13.5.6-1-x
skip-checks: true
2025-08-22 14:13:55 +00:00
henrikdent 0c3a71145a Add DENTNet ledger support (via generic Polkadot app) (#1942) 2025-08-22 11:10:05 -03:00
github-actions[bot] 9c1be4f19b [CI Skip] bump/beta 13.5.6-0-x
skip-checks: true
2025-08-20 15:59:22 +00:00
Tarik GulandValentin Fernandez e5e6bdd814 Set pbkdf2Encode rounds to default to 210,000 (#1983)
* Set pbkdf2Encode rounds to default to 210,000

* Cap amount of rounds to 2048 when onlyJS = true to prevent overflow issues.

* Patch tests to use a consistent amount of encoding rounds.

* lint

* Add additional docs

* lint

---------

Co-authored-by: Valentin Fernandez <tinchofernandez8@gmail.com>
2025-08-20 12:55:25 -03:00
github-actions[bot] 55a3201bd5 [CI Skip] release/stable 13.5.5
skip-checks: true
2025-08-11 15:47:48 +00:00
Valentin Fernandez de2538c18d 13.5.5. (#2000) 2025-08-11 12:44:05 -03:00
github-actions[bot] e8dced9fed [CI Skip] bump/beta 13.5.5-0-x
skip-checks: true
2025-08-04 17:54:26 +00:00
José Molina ColmeneroandValentin Fernandez 0afe1144d1 Add ledger support for Mythos (#1969)
* Add ledger support for Mythos

* Fix linting issues

* Remove Mythos from prevLedgerRecord

* allow mythos ss58

* ss58 Exceptions list

---------

Co-authored-by: Valentin Fernandez <tinchofernandez8@gmail.com>
2025-08-04 14:50:37 -03:00
github-actions[bot] 5a04d67bb2 [CI Skip] release/stable 13.5.4
skip-checks: true
2025-07-28 16:16:48 +00:00
Arjun Porwal 41481c1e3a 13.5.4 (#1999) 2025-07-28 21:43:02 +05:30
github-actions[bot] 8a8b583f81 [CI Skip] bump/beta 13.5.4-0-x
skip-checks: true
2025-07-28 15:59:37 +00:00
SalinaandBeqa Abuladze 03c8169cd9 Add Creditcoin3 support to Generic Ledger App (#1985)
* Update genesis.ts with creditcoin3 geneis hash

* Update defaults.ts with a Creditcoin3

* Update ledger.ts with creditcoin3

* Update defaults.ts to move creditcoin3 in sorted order

* chore: move creditcoin3 to genericLedgerApps

* chore: add cc3 testnet, drynet and devnet

* chore: yarn lint

---------

Co-authored-by: Beqa Abuladze <beqaabuladze.00@gmail.com>
2025-07-28 21:25:51 +05:30
github-actions[bot] f9ee358442 [CI Skip] release/stable 13.5.3
skip-checks: true
2025-07-01 15:36:43 +00:00
rajk93 ef5cfc728c 13.5.3 (#1996) 2025-07-01 12:32:55 -03:00
github-actions[bot] 88b8a54018 [CI Skip] bump/beta 13.5.3-2-x
skip-checks: true
2025-07-01 14:46:07 +00:00
rajk93 8c666725b2 chore: update polkadot dependencies (#1995) 2025-07-01 11:42:12 -03:00
github-actions[bot] daabdfac45 [CI Skip] bump/beta 13.5.3-1-x
skip-checks: true
2025-06-30 10:23:33 +00:00
Arjun Porwal 538359fd49 Add Optional Strict Input Checking to u8aToU8a (#1994)
* fix: add error handling for null and undefined inputs in u8aToU8a function

* fix: update u8aToU8a function to handle null/undefined inputs with strict checking

* docs: update documentation for u8aToU8a
2025-06-30 15:49:42 +05:30
github-actions[bot] df044669e2 [CI Skip] bump/beta 13.5.3-0-x
skip-checks: true
2025-06-18 16:10:04 +00:00
Arjun Porwal 60374f417a Update scrypt parameter validation (#1993)
* feat: update scrypt parameter validation to use ALLOWED_PARAMS

* fix: lint
2025-06-18 21:36:13 +05:30
github-actions[bot] 5235009f4c [CI Skip] release/stable 13.5.2
skip-checks: true
2025-06-17 14:57:41 +00:00
Valentin Fernandez 21bfff5a51 13.5.2 (#1992) 2025-06-17 11:54:01 -03:00
github-actions[bot] 963025daab [CI Skip] bump/beta 13.5.2-2-x
skip-checks: true
2025-06-17 04:38:45 +00:00
rajk93 ec7ef17b64 chore: add error handling for secure rng in react native (#1991) 2025-06-17 10:04:59 +05:30
github-actions[bot] ef94fed1fe [CI Skip] bump/beta 13.5.2-1-x
skip-checks: true
2025-06-13 13:20:09 +00:00
Arjun Porwal 43ac361806 Add input length validation in scryptFromU8a function (#1990)
* fix: add input length validation in scryptFromU8a function

* fix: improve input validation in scryptFromU8a function

* fix: linting

* fix: update default Scrypt parameters for improved security

* revert: 9070615

* fix: remove dymanic calculation
2025-06-13 10:16:25 -03:00
github-actions[bot] 2bc18cfdbb [CI Skip] bump/beta 13.5.2-0-x
skip-checks: true
2025-05-26 12:10:45 +00:00
Radek Bochenek be7df03e7c feat: add Liberland support to Generic Ledger App (#1989) 2025-05-26 08:06:55 -04:00
github-actions[bot] 0ae44ed34e [CI Skip] release/stable 13.5.1
skip-checks: true
2025-05-19 15:50:11 +00:00
Tarik Gul 09d54ea200 13.5.1 (#1988) 2025-05-19 18:46:33 +03:00
github-actions[bot] 457ea1a0b7 [CI Skip] bump/beta 13.4.5-1-x
skip-checks: true
2025-05-19 15:20:57 +00:00
Tarik Gul 972363a5bb Bump yarn to 4.9.1 (#1987) 2025-05-19 18:17:15 +03:00
github-actions[bot] e59fe6fd22 [CI Skip] bump/beta 13.4.5-0-x
skip-checks: true
2025-05-19 04:16:27 +00:00
Tarik Gul 35f9f4468b Ledger ECDSA signing support (#1986)
* Ledger ECDSA signing support

* Updates inline docs

* linter
2025-05-19 07:12:41 +03:00
github-actions[bot] 802174d6ad [CI Skip] release/stable 13.4.4
skip-checks: true
2025-04-14 13:38:40 +00:00
Valentin Fernandez d690abea02 13.4.4 (#1984) 2025-04-14 10:35:01 -03:00
github-actions[bot] 26a762ae2e [CI Skip] bump/beta 13.4.4-0-x
skip-checks: true
2025-04-11 11:40:53 +00:00
Tarik Gul 9d49bcde75 Improve objectSpread function property handling (#1982)
* Improve objectSpread function property handling

* fix linter
2025-04-11 14:37:13 +03:00
github-actions[bot] 8b0f5bf46e [CI Skip] release/stable 13.4.3
skip-checks: true
2025-02-17 17:14:33 +00:00
Tarik Gul a75d8761da 13.4.3 (#1981) 2025-02-17 19:10:53 +02:00
github-actions[bot] a58fb0bbff [CI Skip] bump/beta 13.4.3-0-x
skip-checks: true
2025-02-17 16:45:40 +00:00
Valentin Fernandez 0467c410c8 Rollback ws change on x-ws (#1980) 2025-02-17 11:41:53 -05:00
github-actions[bot] 095095074e [CI Skip] release/stable 13.4.2
skip-checks: true
2025-02-17 14:32:08 +00:00
Tarik Gul 18a6764841 13.4.2 (#1979) 2025-02-17 16:28:27 +02:00
github-actions[bot] 0e0de687c3 [CI Skip] bump/beta 13.4.2-0-x
skip-checks: true
2025-02-17 14:14:17 +00:00
Valentin Fernandez 6197a897aa Cast ws as global.WebSocket (#1978) 2025-02-17 09:10:28 -05:00
github-actions[bot] 0f7954a89b [CI Skip] release/stable 13.4.1
skip-checks: true
2025-02-17 01:55:44 +00:00
Tarik Gul e056618d2e 13.4.1 (#1977)
* 13.4.1

* Update changelog
2025-02-17 03:51:57 +02:00
github-actions[bot] 82a485cc9c [CI Skip] bump/beta 13.3.2-4-x
skip-checks: true
2025-02-16 17:40:17 +00:00
Valentin Fernandezandtarikgul 2cbea7bf8d Fix Misclassification of ECDSA Signatures in verifyMultisig() (#1973)
* Fix multisignature routing to properly detect signature type

* Improve testing suite

* update dep in tests

* lint

* revert changes to tests

* Add reprocible test

---------

Co-authored-by: tarikgul <tariksnow37@gmail.com>
2025-02-16 12:36:35 -05:00
github-actions[bot] 748f670351 [CI Skip] bump/beta 13.3.2-3-x
skip-checks: true
2025-02-12 20:13:38 +00:00
Valentin Fernandez 5b2749073f Fix: Use ws library in Node.js 22+ for Better WebSocket Error Handling (#1976)
* Fallback to ws library on NodeJS 22+

* Add github issue reference to docs
2025-02-12 15:09:56 -05:00
github-actions[bot] a328ab4409 [CI Skip] bump/beta 13.3.2-2-x
skip-checks: true
2025-02-05 11:45:04 +00:00
Valentin Fernandez e25bb48ba9 Allow keyringPair to create using a mnemonic wordlist (#1974) 2025-02-05 06:41:14 -05:00
github-actions[bot] 8456870148 [CI Skip] bump/beta 13.3.2-1-x
skip-checks: true
2025-01-17 17:42:50 +00:00
Tarik Gul 6f8caad80f Bump dev to 0.83.2 (#1972)
* Bump dev to 0.83.1

* Test deno version

* test with deno 2.1.4

* correct version

* 1.46.3

* Up to 0.83.2
2025-01-17 19:39:05 +02:00
github-actions[bot] 25fe5f4e27 [CI Skip] bump/beta 13.3.2-0-x
skip-checks: true
2025-01-06 16:23:17 +00:00
Tarik Gul da2edf0aa0 Bump yarn to 4.6.0 (#1967) 2025-01-06 18:16:58 +02:00
github-actions[bot] eb7b24f9aa [CI Skip] release/stable 13.3.1
skip-checks: true
2025-01-06 14:53:56 +00:00
Tarik Gul f4e93cf2c2 13.3.1 (#1966) 2025-01-06 16:47:34 +02:00
github-actions[bot] 85b2da734c [CI Skip] bump/beta 13.2.4-0-x
skip-checks: true
2025-01-02 15:43:59 +00:00
Tarik Gul 5d03be1084 Fix toLocaleString for v22.12 (#1965)
* Fix toLocaleString for v22.12

* fix ci

* Update tests, and cleanup solution

* remove ar locale
2025-01-02 17:37:40 +02:00
Tarik Gul 2261862097 2025 (#1963)
* 2025

* Set CI version so header passes
2025-01-01 20:53:24 +02:00
644 changed files with 52029 additions and 2007 deletions
+2 -1
View File
@@ -19,9 +19,10 @@ jobs:
- uses: denoland/setup-deno@v1
with:
# Deno v2 throws errors with specific types therefore we set it to the last version before v2
deno-version: v1.42.x
deno-version: v1.46.3
- name: ${{ matrix.step }}
if: always()
run: |
deno --version
yarn install --immutable
yarn ${{ matrix.step }}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util authors & contributors
// Copyright 2017-2026 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
module.exports = require('@polkadot/dev/config/prettier.cjs');
-934
View File
File diff suppressed because one or more lines are too long
+948
View File
File diff suppressed because one or more lines are too long
+10 -1
View File
@@ -12,4 +12,13 @@ logFilters:
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.5.1.cjs
yarnPath: .yarn/releases/yarn-4.9.1.cjs
# @quantus packages come from the Gitea registry. Safe as a whole-scope route
# because we own every name in it — unlike @polkadot, where routing the scope
# would send yarn looking for dozens of unforked packages that are not there.
#
# Reads are anonymous; publishing needs `pass gitea/package-publisher-token`.
npmScopes:
quantus:
npmRegistryServer: "https://git.lair.cafe/api/packages/quantus/npm/"
+139
View File
@@ -1,5 +1,144 @@
# CHANGELOG
## 14.0.3 Mar 23, 2026
Changes:
- Fix(hw-ledger): reset transport on operation errors and add explicit disconnect API ([#2024](https://github.com/polkadot-js/common/pull/2024))
## 14.0.2 Mar 16, 2026
Changes:
- Set headers to 2026 ([#2021](https://github.com/polkadot-js/common/pull/2021))
Contributed:
- fix(util-crypto): use correct x/y getters in secp256k1Expand ([#2018](https://github.com/polkadot-js/common/pull/2018)) (Thanks to https://github.com/AndreasGassmann)
## 14.0.1 Dec 9, 2025
Changes:
- fix(x-randomvalues): prioritize native RN modules over polyfilled crypto ([#2013](https://github.com/polkadot-js/common/pull/2013))
- sr25519: switch from wasm to micro-sr25519 ([#1971](https://github.com/polkadot-js/common/pull/1971))
## 13.5.9 Nov 25, 2025
Changes:
- Bump polkadot dependencies ([#2011](https://github.com/polkadot-js/common/pull/2011))
## 13.5.8 Nov 11, 2025
Changes:
- Bump @polkadot dependencies ([#2009](https://github.com/polkadot-js/common/pull/2009))
## 13.5.7 Oct 13, 2025
Changes:
- Revert "Set pbkdf2Encode rounds to default to 210,000" ([#2007](https://github.com/polkadot-js/common/pull/2007))
## 13.5.6 Aug 26, 2025
Changes:
- Set pbkdf2Encode rounds to default to 210,000 ([#1983](https://github.com/polkadot-js/common/pull/1983))
- Bump @polkadot/wasm deps ([#2002](https://github.com/polkadot-js/common/pull/2002))
Contributed:
- Add DENTNet to generic Polkadot app supported chains ([#1942](https://github.com/polkadot-js/common/pull/1942))
## 13.5.5 Aug 11, 2025
Changes:
- Add ledger support for Mythos ([#1969](https://github.com/polkadot-js/common/pull/1969))
## 13.5.4 July 28, 2025
Changes:
- Add Creditcoin3 support to Generic Ledger App ([#1985](https://github.com/polkadot-js/common/pull/1985))
## 13.5.3 July 1, 2025
Changes:
- Update polkadot dependencies ([#1995](https://github.com/polkadot-js/common/pull/1995))
- Add Optional Strict Input Checking to u8aToU8a ([#1994](https://github.com/polkadot-js/common/pull/1994))
- Update scrypt parameter validation ([#1993](https://github.com/polkadot-js/common/pull/1993))
## 13.5.2 Jun 17, 2025
Changes:
- fix: enhance error handling for secure random number generation in React Native ([#1991](https://github.com/polkadot-js/common/pull/1991))
- Add input length validation in scryptFromU8a function ([#1990](https://github.com/polkadot-js/common/pull/1990))
Contributed:
- Add Liberland support to Generic Ledger App ([#1989](https://github.com/polkadot-js/common/pull/1989))
## 13.5.1 May 19, 2025
Changes:
- Ledger ECDSA signing support ([#1986](https://github.com/polkadot-js/common/pull/1986))
- Bump yarn to 4.9.1 ([#1987](https://github.com/polkadot-js/common/pull/1987))
## 13.4.4 Apr 14, 2025
Changes:
- Improve objectSpread function property handling ([#1982](https://github.com/polkadot-js/common/pull/1982))
## 13.4.3 Feb 17, 2025
Changes:
- Rollback ws change in x-ws ([#1980](https://github.com/polkadot-js/common/pull/1980))
NOTE: this rollbacks changes made in ([#1976](https://github.com/polkadot-js/common/pull/1976)) to ensure websocket usage doesnt break in any downstream libs.
## 13.4.2 Feb 17, 2025
Changes:
- Cast ws as global.WebSocket ([#1978](https://github.com/polkadot-js/common/pull/1978))
## 13.4.1 Feb 16, 2025
Changes:
- Enable KeyringPair Creation with Custom Mnemonic Wordlists ([#1974](https://github.com/polkadot-js/common/pull/1974))
- Use ws library in Node.js 22+ for Better WebSocket Error Handling ([#1976](https://github.com/polkadot-js/common/pull/1976))
- Fix Misclassification of ECDSA Signatures in `verifyMultisig` ([#1973](https://github.com/polkadot-js/common/pull/1973))
- Bump dev to 0.83.2 ([#1972](https://github.com/polkadot-js/common/pull/1972))
- Bump yarn to 4.6.0 ([#1967](https://github.com/polkadot-js/common/pull/1967))
## 13.3.1 Jan 6, 2025
Changes:
- Give compatibility for nodejs v22
- Fix `getSeparator` which fixes toLocaleString ([#1965](https://github.com/polkadot-js/common/pull/1965))
- 2025 Headers ([#1963](https://github.com/polkadot-js/common/pull/1963))
## 13.2.3 Nov 11, 2024
Contributed:
+11 -2
View File
@@ -1,14 +1,17 @@
1674 Jaco Bump deps (#1912)
31 Tarik Gul 13.2.3 (#1960)
47 Tarik Gul 14.0.3 (#2025)
13 Carlo Sala feat(networks): add zeitgeist Ledger support (#1840)
11 Valentin Fernandez 13.5.7 (#2008)
9 Antoine Estienne Add eth uri keyring test (#1291)
9 rajk93 14.0.1 (#2014)
6 Amaury Martiny Add tests from Rust (#283)
6 Jakub Pánik HydraDX -> Hydration rebrand (#1923)
5 Arjun Porwal 13.5.6 (#2004)
5 kwingram25 Add BN consts and timeToString (#610)
4 Adam Dossa Update Polymesh Genesis Hash (#1193)
4 pan add bifrost kusama (#1626)
3 AndreasGassmann fix(util-crypto): use correct x/y getters in secp256k1Expand (#2018)
3 Luke Schoen docs: Fix example in readme since we use default imports (#243)
2 AndreasGassmann feat(dependencies): remove moment.js (#530)
2 Cameron Fairchild Add bittensor details (#1783)
2 Gav Wood Fix stake:unstake (#182)
2 Hyungsuk Kang Add cross client Encryption/Decryption (#496)
@@ -29,15 +32,18 @@
1 Chris Get rid of warnings for dual esm and cjs packages of the same version (#1890)
1 Dmitrii Novikov Add `Vara` support to Generic Ledger App (#1958)
1 Eliott Teissonniere add nodle ledger specs (#1089)
1 Francis O'Brien Fix(hw-ledger): reset transport on operation errors and add explicit disconnect API (#2024)
1 Guilane DENGUIR feat(VTB): Added support for VTB blockchain (#1651)
1 h4x3rotab Throws an error when signing with a locked keypair (#578)
1 Harry Liu Update README.md (#446)
1 henrikdent Add DENTNet ledger support (via generic Polkadot app) (#1942)
1 Hoon Kim replace plasm genesis with astar (#1397)
1 Hussein Ait-Lahcen Add Picasso Genesis Hash (#1261)
1 Igor Support genshiro network (#1148)
1 Jake Naviasky Add evmToAddress and addressToEvm crypto utils. (#705)
1 JesseAbram added check if valid address to docs (#440)
1 joe petrowski Add example for multisig address creation (#590)
1 José Molina Colmenero Add ledger support for Mythos (#1969)
1 kaelnew add ledger support for stafi (#1569)
1 Leonardo Custodio Ledger support (#1879)
1 Leonardo Razovic feat: add Polimec (#1945)
@@ -49,12 +55,15 @@
1 Mohsan Riaz Add Ternoa for ledger hardware support (#1711)
1 nahuseyoum Reserving 65 for Aventus based on the new extended range (#871)
1 Nantian add acala ledger config (#1485)
1 Paul Miller sr25519: switch from wasm to micro-sr25519 (#1971)
1 peetzweg/ adds `isRiscV` guard function similar to isWasm (#1878)
1 qwer951123 Add acala genesis (#1161)
1 Radek Bochenek feat: add Liberland support to Generic Ledger App (#1989)
1 Raoul Millais Fix yarn add instructions in x-global README (#873)
1 Raymond Zhong Add Edgeware ledger app (#1006)
1 Reto Trinkler Adding 4 as address type of ss58 for Katal Chain (#639)
1 Rocco Musolino remove doc broken link (#697)
1 Salina Add Creditcoin3 support to Generic Ledger App (#1985)
1 Shamilkhan Add genesis hash for Cere Network (#1806)
1 Song Zhou Add in ability to encrypt/decrypt msg with keyring (#1070)
1 Thibaut Sardan add hasLedgerSupport prop to networks (#835)
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util authors & contributors
// Copyright 2017-2026 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import baseConfig from '@polkadot/dev/config/eslint';
+5 -5
View File
@@ -6,7 +6,7 @@
},
"homepage": "https://github.com/polkadot-js/common#readme",
"license": "Apache-2.0",
"packageManager": "yarn@4.5.1",
"packageManager": "yarn@4.9.1",
"private": true,
"repository": {
"type": "git",
@@ -14,10 +14,10 @@
},
"sideEffects": false,
"type": "module",
"version": "13.2.3",
"version": "14.0.3",
"versions": {
"git": "13.2.3",
"npm": "13.2.3"
"git": "14.0.3",
"npm": "14.0.3"
},
"workspaces": [
"packages/*"
@@ -37,7 +37,7 @@
"test:one": "polkadot-dev-run-test --env browser"
},
"devDependencies": {
"@polkadot/dev": "^0.82.1",
"@polkadot/dev": "^0.83.3",
"@types/node": "^22.7.5"
},
"resolutions": {
+2 -2
View File
@@ -14,7 +14,7 @@
},
"sideEffects": false,
"type": "module",
"version": "13.2.3",
"version": "14.0.3",
"browser": "browser.js",
"main": "node.js",
"react-native": "react-native.js",
@@ -22,7 +22,7 @@
"@ledgerhq/hw-transport": "^6.31.4",
"@ledgerhq/hw-transport-webhid": "^6.29.4",
"@ledgerhq/hw-transport-webusb": "^6.29.4",
"@polkadot/util": "13.2.3",
"@polkadot/util": "14.0.3",
"tslib": "^2.8.0"
},
"optionalDependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
import LedgerHid from '@ledgerhq/hw-transport-webhid';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { createDefs } from './util.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './empty.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
import LedgerHid from '@ledgerhq/hw-transport-node-hid-singleton';
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger-transports authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger-transports authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
@@ -1,6 +1,6 @@
// Copyright 2017-2024 @polkadot/hw-ledger-transports authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger-transports authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
export const packageInfo = { name: '@polkadot/hw-ledger-transports', path: 'auto', type: 'auto', version: '13.2.3' };
export const packageInfo = { name: '@polkadot/hw-ledger-transports', path: 'auto', type: 'auto', version: '14.0.3' };
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './empty.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
// CJS, so we use import * syntax
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Transport, TransportDef, TransportType } from './types.js';
+4 -4
View File
@@ -17,12 +17,12 @@
"./packageDetect.cjs"
],
"type": "module",
"version": "13.2.3",
"version": "14.0.3",
"main": "index.js",
"dependencies": {
"@polkadot/hw-ledger-transports": "13.2.3",
"@polkadot/util": "13.2.3",
"@zondax/ledger-substrate": "1.0.0",
"@polkadot/hw-ledger-transports": "14.0.3",
"@polkadot/util": "14.0.3",
"@zondax/ledger-substrate": "1.1.1",
"tslib": "^2.8.0"
}
}
+29 -4
View File
@@ -1,8 +1,8 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubstrateApp } from '@zondax/ledger-substrate';
import type { TransportDef, TransportType } from '@polkadot/hw-ledger-transports/types';
import type { Transport, TransportDef, TransportType } from '@polkadot/hw-ledger-transports/types';
import type { AccountOptions, LedgerAddress, LedgerSignature, LedgerVersion } from './types.js';
import { newSubstrateApp } from '@zondax/ledger-substrate';
@@ -30,6 +30,17 @@ async function wrapError <T extends WrappedResult> (promise: Promise<T>): Promis
return result;
}
/** @internal Best-effort transport cleanup after failed operations */
async function closeTransport (transport: Transport | null): Promise<void> {
if (transport) {
try {
await transport.close();
} catch {
// Ignore cleanup errors so the original failure is preserved.
}
}
}
/** @internal Wraps a sign/signRaw call and returns the associated signature */
function sign (method: 'sign' | 'signRaw', message: Uint8Array, accountOffset = 0, addressOffset = 0, { account = LEDGER_DEFAULT_ACCOUNT, addressIndex = LEDGER_DEFAULT_INDEX, change = LEDGER_DEFAULT_CHANGE }: Partial<AccountOptions> = {}): (app: SubstrateApp) => Promise<LedgerSignature> {
return async (app: SubstrateApp): Promise<LedgerSignature> => {
@@ -114,16 +125,29 @@ export class Ledger {
return this.withApp(sign('signRaw', u8aWrapBytes(message), accountOffset, addressOffset, options));
}
/**
* Closes any active transport connection
*/
public async disconnect (): Promise<void> {
const app = this.#app;
this.#app = null;
await closeTransport(app?.transport || null);
}
/**
* @internal
*
* Returns a created SubstrateApp to perform operations against. Generally
* this is only used internally, to ensure consistent bahavior.
* this is only used internally, to ensure consistent behavior.
*/
async withApp <T> (fn: (app: SubstrateApp) => Promise<T>): Promise<T> {
let transport: Transport | null = null;
try {
if (!this.#app) {
const transport = await this.#transportDef.create();
transport = await this.#transportDef.create();
// We need this override for the actual type passing - the Deno environment
// is quite a bit stricter and it yields invalids between the two (specifically
@@ -136,6 +160,7 @@ export class Ledger {
return await fn(this.#app);
} catch (error) {
await closeTransport(this.#app?.transport || transport);
this.#app = null;
throw error;
+111 -12
View File
@@ -1,7 +1,7 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { TransportDef, TransportType } from '@polkadot/hw-ledger-transports/types';
import type { Transport, TransportDef, TransportType } from '@polkadot/hw-ledger-transports/types';
import type { AccountOptionsGeneric, LedgerAddress, LedgerSignature, LedgerVersion } from './types.js';
import { PolkadotGenericApp } from '@zondax/ledger-substrate';
@@ -33,7 +33,7 @@ async function wrapError <T extends WrappedResult> (promise: Promise<T>): Promis
try {
result = await promise;
} catch (e: unknown) {
// We check to see if the propogated error is the newer ResponseError type.
// We check to see if the propagated error is the newer ResponseError type.
// The response code use to be part of the result, but with the latest breaking changes from 0.42.x
// the interface and it's types have completely changed.
if ((e as ResponseError).returnCode) {
@@ -46,8 +46,19 @@ async function wrapError <T extends WrappedResult> (promise: Promise<T>): Promis
return result;
}
/** @internal Wraps a sign/signRaw call and returns the associated signature */
function sign (method: 'sign' | 'signRaw', message: Uint8Array, slip44: number, accountIndex = 0, addressOffset = 0): (app: PolkadotGenericApp) => Promise<LedgerSignature> {
/** @internal Best-effort transport cleanup after failed operations */
async function closeTransport (transport: Transport | null): Promise<void> {
if (transport) {
try {
await transport.close();
} catch {
// Ignore cleanup errors so the original failure is preserved.
}
}
}
/** @internal Wraps a signEd25519/signRawEd25519 call and returns the associated signature */
function sign (method: 'signEd25519' | 'signRawEd25519', message: Uint8Array, slip44: number, accountIndex = 0, addressOffset = 0): (app: PolkadotGenericApp) => Promise<LedgerSignature> {
const bip42Path = `m/44'/${slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
return async (app: PolkadotGenericApp): Promise<LedgerSignature> => {
@@ -59,7 +70,22 @@ function sign (method: 'sign' | 'signRaw', message: Uint8Array, slip44: number,
};
}
/** @internal Wraps a signWithMetadata call and returns the associated signature */
/** @internal Wraps a signEcdsa/signRawEcdsa call and returns the associated signature */
function signEcdsa (method: 'signEcdsa' | 'signRawEcdsa', message: Uint8Array, slip44: number, accountIndex = 0, addressOffset = 0): (app: PolkadotGenericApp) => Promise<LedgerSignature> {
const bip42Path = `m/44'/${slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
return async (app: PolkadotGenericApp): Promise<LedgerSignature> => {
const { r, s, v } = await wrapError(app[method](bip42Path, u8aToBuffer(message)));
const signature = Buffer.concat([r, s, v]);
return {
signature: hexAddPrefix(signature.toString('hex'))
};
};
}
/** @internal Wraps a signWithMetadataEd25519 call and returns the associated signature */
function signWithMetadata (message: Uint8Array, slip44: number, accountIndex = 0, addressOffset = 0, { metadata }: Partial<AccountOptionsGeneric> = {}): (app: PolkadotGenericApp) => Promise<LedgerSignature> {
const bip42Path = `m/44'/${slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
@@ -70,7 +96,28 @@ function signWithMetadata (message: Uint8Array, slip44: number, accountIndex = 0
const bufferMsg = Buffer.from(message);
const { signature } = await wrapError(app.signWithMetadata(bip42Path, bufferMsg, metadata));
const { signature } = await wrapError(app.signWithMetadataEd25519(bip42Path, bufferMsg, metadata));
return {
signature: hexAddPrefix(signature.toString('hex'))
};
};
}
/** @internal Wraps a signWithMetadataEcdsa call and returns the associated signature */
function signWithMetadataEcdsa (message: Uint8Array, slip44: number, accountIndex = 0, addressOffset = 0, { metadata }: Partial<AccountOptionsGeneric> = {}): (app: PolkadotGenericApp) => Promise<LedgerSignature> {
const bip42Path = `m/44'/${slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
return async (app: PolkadotGenericApp): Promise<LedgerSignature> => {
if (!metadata) {
throw new Error('The metadata option must be present when using signWithMetadata');
}
const bufferMsg = Buffer.from(message);
const { r, s, v } = await wrapError(app.signWithMetadataEcdsa(bip42Path, bufferMsg, metadata));
const signature = Buffer.concat([r, s, v]);
return {
signature: hexAddPrefix(signature.toString('hex'))
@@ -119,14 +166,31 @@ export class LedgerGeneric {
}
/**
* @description Returns the address associated with a specific account & address offset. Optionally
* @description Returns the address associated with a specific Ed25519 account & address offset. Optionally
* asks for on-device confirmation
*/
public async getAddress (ss58Prefix: number, confirm = false, accountIndex = 0, addressOffset = 0): Promise<LedgerAddress> {
const bip42Path = `m/44'/${this.#slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
return this.withApp(async (app: PolkadotGenericApp): Promise<LedgerAddress> => {
const { address, pubKey } = await wrapError(app.getAddress(bip42Path, ss58Prefix, confirm));
const { address, pubKey } = await wrapError(app.getAddressEd25519(bip42Path, ss58Prefix, confirm));
return {
address,
publicKey: hexAddPrefix(pubKey)
};
});
}
/**
* @description Returns the address associated with a specific ecdsa account & address offset. Optionally
* asks for on-device confirmation
*/
public async getAddressEcdsa (confirm = false, accountIndex = 0, addressOffset = 0) {
const bip42Path = `m/44'/${this.#slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
return this.withApp(async (app: PolkadotGenericApp): Promise<LedgerAddress> => {
const { address, pubKey } = await wrapError(app.getAddressEcdsa(bip42Path, confirm));
return {
address,
@@ -154,14 +218,28 @@ export class LedgerGeneric {
* @description Signs a transaction on the Ledger device. This requires the LedgerGeneric class to be instantiated with `chainId`, and `metaUrl`
*/
public async sign (message: Uint8Array, accountIndex?: number, addressOffset?: number): Promise<LedgerSignature> {
return this.withApp(sign('sign', message, this.#slip44, accountIndex, addressOffset));
return this.withApp(sign('signEd25519', message, this.#slip44, accountIndex, addressOffset));
}
/**
* @description Signs a message (non-transactional) on the Ledger device
*/
public async signRaw (message: Uint8Array, accountIndex?: number, addressOffset?: number): Promise<LedgerSignature> {
return this.withApp(sign('signRaw', u8aWrapBytes(message), this.#slip44, accountIndex, addressOffset));
return this.withApp(sign('signRawEd25519', u8aWrapBytes(message), this.#slip44, accountIndex, addressOffset));
}
/**
* @description Signs a transaction on the Ledger device with Ecdsa. This requires the LedgerGeneric class to be instantiated with `chainId`, and `metaUrl`
*/
public async signEcdsa (message: Uint8Array, accountIndex?: number, addressOffset?: number): Promise<LedgerSignature> {
return this.withApp(signEcdsa('signEcdsa', u8aWrapBytes(message), this.#slip44, accountIndex, addressOffset));
}
/**
* @description Signs a message with Ecdsa (non-transactional) on the Ledger device
*/
public async signRawEcdsa (message: Uint8Array, accountIndex?: number, addressOffset?: number): Promise<LedgerSignature> {
return this.withApp(signEcdsa('signRawEcdsa', u8aWrapBytes(message), this.#slip44, accountIndex, addressOffset));
}
/**
@@ -171,6 +249,24 @@ export class LedgerGeneric {
return this.withApp(signWithMetadata(message, this.#slip44, accountIndex, addressOffset, options));
}
/**
* @description Signs a transaction on the ledger device for an ecdsa signature provided some metadata.
*/
public async signWithMetadataEcdsa (message: Uint8Array, accountIndex?: number, addressOffset?: number, options?: Partial<AccountOptionsGeneric>) {
return this.withApp(signWithMetadataEcdsa(message, this.#slip44, accountIndex, addressOffset, options));
}
/**
* Closes any active transport connection
*/
public async disconnect (): Promise<void> {
const app = this.#app;
this.#app = null;
await closeTransport(app?.transport || null);
}
/**
* @internal
*
@@ -178,9 +274,11 @@ export class LedgerGeneric {
* this is only used internally, to ensure consistent bahavior.
*/
async withApp <T> (fn: (app: PolkadotGenericApp) => Promise<T>): Promise<T> {
let transport: Transport | null = null;
try {
if (!this.#app) {
const transport = await this.#transportDef.create();
transport = await this.#transportDef.create();
// We need this override for the actual type passing - the Deno environment
// is quite a bit stricter and it yields invalids between the two (specifically
@@ -193,6 +291,7 @@ export class LedgerGeneric {
return await fn(this.#app);
} catch (error) {
await closeTransport(this.#app?.transport || transport);
this.#app = null;
throw error;
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
// This is necessary to ensure users still have access to class Ledger even though its deprecated.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
export const LEDGER_DEFAULT_ACCOUNT = 0x80000000;
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+5 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
// These map to the known name in the @zondax/ledger-substrate/supported_apps package
@@ -51,9 +51,13 @@ export const prevLedgerRecord: Record<string, string> = {
// Any chains moving forward that are supported by the PolkadotGenericApp from ledger will input their names below.
export const genericLedgerApps = {
bittensor: 'Bittensor',
creditcoin3: 'Creditcoin3',
dentnet: 'DENTNet',
encointer: 'Encointer',
frequency: 'Frequency',
integritee: 'Integritee',
liberland: 'Liberland',
mythos: 'Mythos',
polimec: 'Polimec',
vara: 'Vara'
};
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './packageDetect.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './index.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
export const packageInfo = { name: '@polkadot/hw-ledger', path: 'auto', type: 'auto', version: '13.2.3' };
export const packageInfo = { name: '@polkadot/hw-ledger', path: 'auto', type: 'auto', version: '14.0.3' };
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/hw-ledger authors & contributors
// Copyright 2017-2026 @polkadot/hw-ledger authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '@polkadot/util/types';
+6 -5
View File
@@ -18,15 +18,16 @@
"./packageDetect.cjs"
],
"type": "module",
"version": "13.2.3",
"version": "14.0.3-quantus.2",
"main": "index.js",
"dependencies": {
"@polkadot/util": "13.2.3",
"@polkadot/util-crypto": "13.2.3",
"@polkadot/util": "14.0.3",
"@polkadot/util-crypto": "14.0.3-quantus.2",
"@quantus/crypto": "^0.1.0",
"tslib": "^2.8.0"
},
"peerDependencies": {
"@polkadot/util": "13.2.3",
"@polkadot/util-crypto": "13.2.3"
"@polkadot/util": "14.0.3",
"@polkadot/util-crypto": "14.0.3"
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
// all external
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
// default substrate dev phrase
+34 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
@@ -6,7 +6,8 @@
import type { KeyringPair$Json } from './types.js';
import { hexToU8a, stringToU8a } from '@polkadot/util';
import { base64Decode, cryptoWaitReady, encodeAddress, randomAsU8a, setSS58Format } from '@polkadot/util-crypto';
import { base64Decode, cryptoWaitReady, encodeAddress, mnemonicGenerate, randomAsU8a, setSS58Format } from '@polkadot/util-crypto';
import * as languages from '@polkadot/util-crypto/mnemonic/wordlists/index';
import { decodePair } from './pair/decode.js';
import Keyring from './index.js';
@@ -574,4 +575,35 @@ describe('keypair', (): void => {
expect(pair.address).toBe('FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH');
});
});
describe('wordlist', (): void => {
it('creates keypair from different wordlists mnemonics', (): void => {
Object.keys(languages).forEach((language) => {
const mnemonic = mnemonicGenerate(12, languages[language as keyof typeof languages]);
const keyring = new Keyring({
type: 'ed25519'
});
expect(keyring.addFromMnemonic(
mnemonic,
{},
'ed25519',
languages[language as keyof typeof languages]
)).toBeDefined();
});
});
it('cannot create from invalid wordlist', (): void => {
const mnemonic = mnemonicGenerate(12, languages.japanese);
const keyring = new Keyring({
type: 'ed25519'
});
expect(() => keyring.addFromMnemonic(
mnemonic,
{},
'ed25519',
languages.english
)).toThrow('Invalid bip39 mnemonic specified');
});
});
});
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './packageDetect.js';
+72 -14
View File
@@ -1,17 +1,29 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { EncryptedJsonEncoding, Keypair, KeypairType } from '@polkadot/util-crypto/types';
import type { KeyringInstance, KeyringOptions, KeyringPair, KeyringPair$Json, KeyringPair$Meta } from './types.js';
import { hexToU8a, isHex, stringToU8a } from '@polkadot/util';
import { base64Decode, decodeAddress, ed25519PairFromSeed as ed25519FromSeed, encodeAddress, ethereumEncode, hdEthereum, keyExtractSuri, keyFromPath, mnemonicToLegacySeed, mnemonicToMiniSecret, secp256k1PairFromSeed as secp256k1FromSeed, sr25519PairFromSeed as sr25519FromSeed } from '@polkadot/util-crypto';
import { base64Decode, decodeAddress, dilithiumPairFromMnemonic, dilithiumPairFromSeed, dilithiumPathFromSuri, ed25519PairFromSeed as ed25519FromSeed, encodeAddress, ethereumEncode, hdEthereum, isDilithium, keyExtractSuri, keyFromPath, mnemonicToLegacySeed, mnemonicToMiniSecret, secp256k1PairFromSeed as secp256k1FromSeed, sr25519PairFromSeed as sr25519FromSeed } from '@polkadot/util-crypto';
import { createPair } from './pair/index.js';
import { DEV_PHRASE } from './defaults.js';
import { Pairs } from './pairs.js';
/**
* Every keypair type this keyring will construct.
*
* Was two separate inline arrays, both of which a widened `KeypairType` silently
* walked past — the union is compile-time and these checks are not. Named once so
* the next change to the union has one place to look, and so the error message
* cannot drift from the check.
*/
const KEYPAIR_TYPES: KeypairType[] = ['dilithium65', 'dilithium87', 'ecdsa', 'ed25519', 'ethereum', 'sr25519'];
const PairFromSeed = {
dilithium65: (seed: Uint8Array): Keypair => dilithiumPairFromSeed(seed, 'dilithium65'),
dilithium87: (seed: Uint8Array): Keypair => dilithiumPairFromSeed(seed, 'dilithium87'),
ecdsa: (seed: Uint8Array): Keypair => secp256k1FromSeed(seed),
ed25519: (seed: Uint8Array): Keypair => ed25519FromSeed(seed),
ethereum: (seed: Uint8Array): Keypair => secp256k1FromSeed(seed),
@@ -50,8 +62,11 @@ export class Keyring implements KeyringInstance {
constructor (options: KeyringOptions = {}) {
options.type = options.type || 'ed25519';
if (!['ecdsa', 'ethereum', 'ed25519', 'sr25519'].includes(options.type || 'undefined')) {
throw new Error(`Expected a keyring type of either 'ed25519', 'sr25519', 'ethereum' or 'ecdsa', found '${options.type || 'unknown'}`);
// A runtime whitelist behind the KeypairType union, so widening the type
// alone was not enough — the compiler cannot see this one, and it fails at
// construction rather than at use.
if (!KEYPAIR_TYPES.includes(options.type || ('undefined' as KeypairType))) {
throw new Error(`Expected a keyring type of one of ${KEYPAIR_TYPES.map((t) => `'${t}'`).join(', ')}, found '${options.type || 'unknown'}'`);
}
this.#pairs = new Pairs();
@@ -121,8 +136,8 @@ export class Keyring implements KeyringInstance {
* of an account backup), and then generates a keyring pair from it that it passes to
* `addPair` to stores in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
*/
public addFromMnemonic (mnemonic: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
return this.addFromUri(mnemonic, meta, type);
public addFromMnemonic (mnemonic: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[]): KeyringPair {
return this.addFromUri(mnemonic, meta, type, wordlist);
}
/**
@@ -153,9 +168,9 @@ export class Keyring implements KeyringInstance {
* @summary Creates an account via an suri
* @description Extracts the phrase, path and password from a SURI format for specifying secret keys `<secret>/<soft-key>//<hard-key>///<password>` (the `///password` may be omitted, and `/<soft-key>` and `//<hard-key>` maybe repeated and mixed). The secret can be a hex string, mnemonic phrase or a string (to be padded)
*/
public addFromUri (suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
public addFromUri (suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[]): KeyringPair {
return this.addPair(
this.createFromUri(suri, meta, type)
this.createFromUri(suri, meta, type, wordlist)
);
}
@@ -175,19 +190,35 @@ export class Keyring implements KeyringInstance {
? [type]
: type;
if (!['ed25519', 'sr25519', 'ecdsa', 'ethereum'].includes(cryptoType)) {
// Same list, second copy — this one guards JSON coming in from a file the
// user chose, so it is the guard that will matter most once quantus/common#6
// narrows the union and imports of quantum-unsafe keys have to be refused
// with a message explaining why rather than "unknown crypto type".
if (!KEYPAIR_TYPES.includes(cryptoType as KeypairType)) {
throw new Error(`Unknown crypto type ${cryptoType}`);
}
// Here the address and publicKey are 32 bytes and isomorphic. This is why the address field needs to be the public key for ethereum type pairs
const publicKey = isHex(address)
// For the curve schemes the address and publicKey are 32 bytes and
// isomorphic, which is why the address field holds the public key for
// ethereum pairs and why this works at all.
const raw = isHex(address)
? hexToU8a(address)
: this.decodeAddress(address, ignoreChecksum);
const decoded = isHex(encoded)
? hexToU8a(encoded)
: base64Decode(encoded);
return createPair({ toSS58: this.encodeAddress, type: cryptoType as KeypairType }, { publicKey, secretKey: new Uint8Array() }, meta, decoded, encType);
// ML-DSA breaks the isomorphism: the account id is a one-way Poseidon2 hash
// and the 1952/2592-byte public key is inside `decoded`, which is encrypted
// and stays that way until someone supplies a password. So pass the account
// id as an account id rather than pretending it is a key — createPair uses
// it for the address while locked, and checks it against the real public key
// the moment `decodePkcs8` produces one.
const info = isDilithium(cryptoType as KeypairType)
? { accountId: raw, publicKey: new Uint8Array(), secretKey: new Uint8Array() }
: { publicKey: raw, secretKey: new Uint8Array() };
return createPair({ toSS58: this.encodeAddress, type: cryptoType as KeypairType }, info, meta, decoded, encType);
}
/**
@@ -203,7 +234,7 @@ export class Keyring implements KeyringInstance {
* @summary Creates a Keypair from an suri
* @description This creates a pair from the suri, but does not add it to the keyring
*/
public createFromUri (_suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
public createFromUri (_suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[]): KeyringPair {
// here we only aut-add the dev phrase if we have a hard-derived path
const suri = _suri.startsWith('//')
? `${DEV_PHRASE}${_suri}`
@@ -212,6 +243,33 @@ export class Keyring implements KeyringInstance {
let seed: Uint8Array;
const isPhraseHex = isHex(phrase, 256);
// ML-DSA derives from the mnemonic itself along a hardened BIP44 path, not
// from a seed along a junction chain — lattice keys have no public
// derivability, so there is no soft junction to emulate and the chain's own
// Pair::derive refuses for the same reason. Handled before the seeding below
// because that seeding (mnemonicToMiniSecret) is the wrong one for us and
// would produce a valid key for an account nobody owns.
if (isDilithium(type)) {
if (isPhraseHex) {
// A raw 32-byte seed goes straight into keygen, which is how the
// dev-genesis accounts are defined. Combining one with a derivation path
// is ambiguous — is the seed the master, or already derived? — so refuse
// rather than pick.
if (derivePath) {
throw new Error('A derivation path cannot be combined with a raw seed for post-quantum pairs');
}
return createPair({ toSS58: this.encodeAddress, type }, PairFromSeed[type](hexToU8a(phrase)), meta, null);
}
return createPair(
{ toSS58: this.encodeAddress, type },
dilithiumPairFromMnemonic(phrase, password || '', dilithiumPathFromSuri(type, derivePath), type),
meta,
null
);
}
if (isPhraseHex) {
seed = hexToU8a(phrase);
} else {
@@ -220,7 +278,7 @@ export class Keyring implements KeyringInstance {
if ([12, 15, 18, 21, 24].includes(parts.length)) {
seed = type === 'ethereum'
? mnemonicToLegacySeed(phrase, '', false, 64)
: mnemonicToMiniSecret(phrase, password);
: mnemonicToMiniSecret(phrase, password, wordlist);
} else {
if (phrase.length > 32) {
throw new Error('specified phrase is not a valid mnemonic and is invalid as a raw seed at > 32 bytes');
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './index.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
export const packageInfo = { name: '@polkadot/keyring', path: 'auto', type: 'auto', version: '13.2.3' };
export const packageInfo = { name: '@polkadot/keyring', path: 'auto', type: 'auto', version: '14.0.3-quantus.2' };
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+31 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { EncryptedJsonEncoding } from '@polkadot/util-crypto/types';
@@ -16,8 +16,20 @@ const SEED_OFFSET = PAIR_HDR.length;
* For divisor/headers, don't rely on the magic being static. These will
* change between generations, aka with the long-awaited 4th generation
* of the format. The external decode interface is the only way to use and decode these.
*
* `secretLength` exists for keys that are not 32 or 64 bytes. The body is
* `HDR ‖ secretKey ‖ DIV ‖ publicKey`, and upstream located the divider by
* trying the two lengths every curve scheme uses. An ML-DSA secret is 4032 or
* 4896 bytes, so neither offset matches and the decode throws "Invalid encoding
* divider found in body" — a stored account that cannot be read back.
*
* The caller passes the length rather than this function searching for the
* divider. Searching would work almost always and fail catastrophically when it
* did not: PAIR_DIV is five bytes, so a 4032-byte secret contains a false match
* about once in 270 million keys, and the result would be a silently wrong key
* rather than an error. The caller knows the type, so it knows the length.
**/
export function decodePair (passphrase?: string, encrypted?: Uint8Array | null, _encType?: EncryptedJsonEncoding | EncryptedJsonEncoding[]): { publicKey: Uint8Array; secretKey: Uint8Array } {
export function decodePair (passphrase?: string, encrypted?: Uint8Array | null, _encType?: EncryptedJsonEncoding | EncryptedJsonEncoding[], secretLength?: number): { publicKey: Uint8Array; secretKey: Uint8Array } {
const encType = Array.isArray(_encType) || _encType === undefined
? _encType
: [_encType];
@@ -29,6 +41,23 @@ export function decodePair (passphrase?: string, encrypted?: Uint8Array | null,
throw new Error('Invalid encoding header found in body');
}
// an explicitly-sized secret (ML-DSA); there is one offset and it either
// matches or the blob is not what the caller said it was
if (secretLength) {
const divOffset = SEED_OFFSET + secretLength;
if (!u8aEq(decrypted.subarray(divOffset, divOffset + PAIR_DIV.length), PAIR_DIV)) {
throw new Error('Invalid encoding divider found in body');
}
return {
// the public key is the remainder: its length varies by scheme and the
// body ends here, so there is nothing to read past it
publicKey: decrypted.subarray(divOffset + PAIR_DIV.length),
secretKey: decrypted.subarray(SEED_OFFSET, divOffset)
};
}
// setup for generation 3 format
let secretKey = decrypted.subarray(SEED_OFFSET, SEED_OFFSET + SEC_LENGTH);
let divOffset = SEED_OFFSET + SEC_LENGTH;
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/** public/secret section divider (generation 1-3, will change in 4, don't rely on value) */
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { contextForSpec, Scheme, sizes } from '@quantus/crypto';
import { dilithiumVerify } from '@polkadot/util-crypto';
import { Keyring } from '../keyring.js';
// crystal_alice, from the chain's dev genesis: a raw 32-byte seed straight into
// ML-DSA-87 keygen, no HD derivation. The expected address is what
// `quantus developer create-test-wallets` prints — an independent implementation,
// not this one.
const ALICE_ADDRESS = 'qzk1Nxai3dZD9Cn5kwGcgL6mKxsfxwqdis7kDQJ52aJS2vSn7';
const MESSAGE = new Uint8Array([0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64]);
describe('dilithium pairs', (): void => {
const keyring = new Keyring({ ss58Format: 189, type: 'dilithium87' });
const alice = keyring.addFromSeed(new Uint8Array(32), { name: 'crystal_alice' }, 'dilithium87');
const context = contextForSpec(148);
const s87 = sizes(Scheme.MlDsa87);
const s65 = sizes(Scheme.MlDsa65);
it('derives the address quantus-cli derives', (): void => {
expect(alice.address).toEqual(ALICE_ADDRESS);
});
// The assumption this whole fork exists to break. Everywhere else in this
// package `addressRaw` and `publicKey` are the same 32 bytes.
it('has an addressRaw that is not the publicKey', (): void => {
expect(alice.addressRaw.length).toEqual(32);
expect(alice.publicKey.length).toEqual(s87.publicKey);
});
it('signs to the wire form the runtime reads', (): void => {
const signature = alice.sign(MESSAGE, { context });
expect(signature.length).toEqual(s87.signatureWithPublicKey);
});
// `withType` prepends the signature enum's variant index. For the curve types
// that enum is Substrate's MultiSignature; here it is the runtime's
// DilithiumSignatureScheme, reached by the same mechanism.
it('prepends the chain variant byte with withType', (): void => {
const signed87 = alice.sign(MESSAGE, { context, withType: true });
const k65 = keyring.addFromSeed(new Uint8Array(32).fill(9), {}, 'dilithium65');
const signed65 = k65.sign(MESSAGE, { context, withType: true });
expect(signed87[0]).toEqual(0);
expect(signed87.length).toEqual(s87.signatureWithPublicKey + 1);
expect(signed65[0]).toEqual(1);
expect(signed65.length).toEqual(s65.signatureWithPublicKey + 1);
});
it('verifies against the account id', (): void => {
const signature = alice.sign(MESSAGE, { context });
expect(dilithiumVerify(MESSAGE, signature, alice.addressRaw, 'dilithium87', context)).toEqual(true);
});
// The spec-148 boundary. A signature made under the wrong context is
// cryptographically valid and rejected by the chain, so this is the only place
// it can be caught locally.
it('does not verify under the wrong context', (): void => {
const signature = alice.sign(MESSAGE, { context });
expect(dilithiumVerify(MESSAGE, signature, alice.addressRaw, 'dilithium87', contextForSpec(147))).toEqual(false);
});
// Verification checks the embedded public key hashes to the account being
// verified against, not merely that the signature is valid for *some* key.
it('does not verify another account signature', (): void => {
const bob = keyring.addFromSeed(new Uint8Array(32).fill(1), {}, 'dilithium87');
const signature = bob.sign(MESSAGE, { context });
expect(dilithiumVerify(MESSAGE, signature, alice.addressRaw, 'dilithium87', context)).toEqual(false);
});
it('refuses to sign without a context', (): void => {
expect(() => alice.sign(MESSAGE)).toThrow(/signing context is required/);
});
// There is no ML-DSA VRF, and faking one from an ordinary signature would
// produce output that looks like a VRF proof and cannot be verified as one.
it('refuses to VRF sign', (): void => {
expect(() => alice.vrfSign(MESSAGE)).toThrow(/not available for dilithium87/);
});
// The classical paths must be untouched — this is what a careless rebase
// breaks, and it would break silently.
it('leaves sr25519 unchanged', (): void => {
const sr = new Keyring({ type: 'sr25519' }).addFromUri('//Alice');
expect(sr.address).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
expect(sr.sign(MESSAGE).length).toEqual(64);
});
});
@@ -0,0 +1,104 @@
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { dilithiumPath } from '@polkadot/util-crypto';
import { Keyring } from '../keyring.js';
// The well-known Substrate development phrase — public by design, so pinning it
// commits no secret. Any account it derives is assumed compromised.
const DEV_PHRASE = 'bottom drive obey lake curtain smoke basket hold race lonely fit walk';
// What `quantus wallet import --mnemonic-file <DEV_PHRASE> --scheme <s>` prints.
// An independent implementation, not this one.
const EXPECT_65 = 'qzq29m9WvneDAeXbtgueKCREtNe1rVVs6bXSMLmjr6shqvwq6';
const EXPECT_87 = 'qzjrYTUnnE5NduTZKxe9dESCMTZg7nTueKM3bwhnkRdD1iYV4';
// crystal_alice — a raw 32-byte seed straight into keygen, no derivation.
const ALICE = 'qzk1Nxai3dZD9Cn5kwGcgL6mKxsfxwqdis7kDQJ52aJS2vSn7';
describe('dilithium derivation', (): void => {
const keyring = new Keyring({ ss58Format: 189, type: 'dilithium65' });
it('builds the paths quantus-cli uses', (): void => {
expect(dilithiumPath('dilithium65')).toEqual("m/44'/189189'/0'/0'/1'");
expect(dilithiumPath('dilithium87')).toEqual("m/44'/189189'/0'/0'/0'");
expect(dilithiumPath('dilithium65', 3)).toEqual("m/44'/189189'/3'/0'/1'");
});
// 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 hash.
it('derives what quantus-cli derives, both schemes', (): void => {
expect(keyring.createFromUri(DEV_PHRASE, {}, 'dilithium65').address).toEqual(EXPECT_65);
expect(keyring.createFromUri(DEV_PHRASE, {}, 'dilithium87').address).toEqual(EXPECT_87);
});
it('defaults to account index 0', (): void => {
expect(keyring.createFromUri(`${DEV_PHRASE}//0`, {}, 'dilithium65').address).toEqual(EXPECT_65);
});
it('derives distinct accounts per index', (): void => {
const zero = keyring.createFromUri(`${DEV_PHRASE}//0`, {}, 'dilithium65').address;
const one = keyring.createFromUri(`${DEV_PHRASE}//1`, {}, 'dilithium65').address;
expect(zero).not.toEqual(one);
});
it('accepts an explicit hardened path', (): void => {
const byIndex = keyring.createFromUri(`${DEV_PHRASE}//2`, {}, 'dilithium65').address;
const byPath = keyring.createFromUri(`${DEV_PHRASE}//m/44'/189189'/2'/0'/1'`, {}, 'dilithium65').address;
expect(byPath).toEqual(byIndex);
});
// The suri syntax was built for curve junctions, where `/foo` is a soft
// derivation over arbitrary bytes. Neither form exists for ML-DSA, and quietly
// reinterpreting one would hand back an address no other tool derives.
it('refuses soft derivation', (): void => {
expect(() => keyring.createFromUri(`${DEV_PHRASE}/0`, {}, 'dilithium65')).toThrow(/Soft derivation is not possible/);
});
it('refuses a named junction', (): void => {
expect(() => keyring.createFromUri(`${DEV_PHRASE}//Alice`, {}, 'dilithium65')).toThrow(/Unsupported derivation path/);
});
it('refuses an unhardened level in an explicit path', (): void => {
expect(() => keyring.createFromUri(`${DEV_PHRASE}//m/44'/189189'/0'/0'/1`, {}, 'dilithium65')).toThrow(/Unhardened derivation/);
});
// A raw seed is already key material. Whether it is the master or something
// already derived is unanswerable, so combining it with a path is refused
// rather than guessed.
it('takes a raw hex seed underived, and refuses to derive from one', (): void => {
const seed = `0x${'00'.repeat(32)}`;
expect(keyring.createFromUri(seed, {}, 'dilithium87').address).toEqual(ALICE);
expect(() => keyring.createFromUri(`${seed}//1`, {}, 'dilithium87')).toThrow(/cannot be combined with a raw seed/);
});
it('honours a BIP39 passphrase', (): void => {
const plain = keyring.createFromUri(DEV_PHRASE, {}, 'dilithium65').address;
const withPass = keyring.createFromUri(`${DEV_PHRASE}///hunter2`, {}, 'dilithium65').address;
expect(plain).not.toEqual(withPass);
});
// Deriving a child *from a pair* is not merely unimplemented here, it is
// impossible: ML-DSA keys are not derivable from one another, and the Quantus
// tree derives each account from the mnemonic independently.
it('refuses to derive from an existing pair', (): void => {
const pair = keyring.createFromUri(DEV_PHRASE, {}, 'dilithium65');
expect(() => pair.derive('//1')).toThrow(/derive from the mnemonic with createFromUri/);
});
it('leaves sr25519 junction derivation unchanged', (): void => {
const sr = new Keyring({ type: 'sr25519' });
expect(sr.createFromUri('//Alice').address).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
expect(sr.createFromUri('//Bob').address).toEqual('5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty');
});
});
@@ -0,0 +1,114 @@
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { contextForSpec } from '@quantus/crypto';
import { dilithiumVerify } from '@polkadot/util-crypto';
import { Keyring } from '../keyring.js';
// The address `quantus developer create-test-wallets` prints for crystal_alice.
const ALICE_ADDRESS = 'qzk1Nxai3dZD9Cn5kwGcgL6mKxsfxwqdis7kDQJ52aJS2vSn7';
const PASSWORD = 'not a good password';
const MESSAGE = new Uint8Array([0x6a, 0x73, 0x6f, 0x6e]);
describe('dilithium account JSON', (): void => {
const keyring = new Keyring({ ss58Format: 189, type: 'dilithium87' });
const context = contextForSpec(148);
const backup = (type: 'dilithium65' | 'dilithium87', fill = 0) => {
const pair = keyring.addFromSeed(new Uint8Array(32).fill(fill), { name: 'backed up' }, type);
return { json: pair.toJson(PASSWORD), original: pair };
};
// The whole point. Before this worked, an exported Quantus account could not be
// imported by anything — decodePair looked for its divider at one of two fixed
// offsets and an ML-DSA secret is at neither, so restore threw "Invalid
// encoding divider found in body". That is a data-durability bug, not a
// convenience one: it had to land before any build let a user create an account.
it('round-trips an ML-DSA-87 account', (): void => {
const { json, original } = backup('dilithium87');
const restored = keyring.createFromJson(json);
restored.decodePkcs8(PASSWORD);
expect(restored.address).toEqual(original.address);
expect(restored.publicKey).toEqual(original.publicKey);
expect(restored.isLocked).toEqual(false);
});
it('round-trips an ML-DSA-65 account', (): void => {
const { json, original } = backup('dilithium65', 5);
const restored = keyring.createFromJson(json);
restored.decodePkcs8(PASSWORD);
expect(restored.address).toEqual(original.address);
expect(restored.publicKey).toEqual(original.publicKey);
});
// A restored pair is locked, and callers read `pair.address` off it long before
// any password appears. For every other scheme that works because the address
// *is* the public key; here the key is still encrypted, so the account id has
// to be carried as data until decodePkcs8 supplies the real one.
it('reports the same address before and after unlock', (): void => {
const { json } = backup('dilithium87');
const restored = keyring.createFromJson(json);
expect(restored.isLocked).toEqual(true);
expect(restored.address).toEqual(ALICE_ADDRESS);
restored.decodePkcs8(PASSWORD);
expect(restored.address).toEqual(ALICE_ADDRESS);
});
it('restores a pair that can still sign', (): void => {
const { json, original } = backup('dilithium87');
const restored = keyring.createFromJson(json);
restored.decodePkcs8(PASSWORD);
const signature = restored.sign(MESSAGE, { context });
expect(dilithiumVerify(MESSAGE, signature, original.addressRaw, 'dilithium87', context)).toEqual(true);
});
it('fails cleanly on a wrong password', (): void => {
const { json } = backup('dilithium87');
const restored = keyring.createFromJson(json);
expect(() => restored.decodePkcs8('wrong')).toThrow();
// and must not have half-applied anything
expect(restored.isLocked).toEqual(true);
expect(restored.address).toEqual(ALICE_ADDRESS);
});
// For every other scheme a tampered `address` field cannot decode at all,
// because the address is the public key. Here it decodes perfectly and yields a
// pair reporting an address its key does not control — a user would see someone
// else's address in their own wallet and believe they held it.
it('rejects JSON whose address does not match its key', (): void => {
const { json } = backup('dilithium87');
const other = keyring.addFromSeed(new Uint8Array(32).fill(2), {}, 'dilithium87');
const tampered = { ...json, address: other.address };
const restored = keyring.createFromJson(tampered);
expect(restored.address).toEqual(other.address);
expect(() => restored.decodePkcs8(PASSWORD)).toThrow(/does not match the address/);
});
it('leaves sr25519 JSON round-tripping unchanged', (): void => {
const sr = new Keyring({ type: 'sr25519' });
const pair = sr.addFromUri('//Alice');
const restored = sr.createFromJson(pair.toJson(PASSWORD));
restored.decodePkcs8(PASSWORD);
expect(restored.address).toEqual(pair.address);
expect(restored.publicKey).toEqual(pair.publicKey);
});
});
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PairInfo } from './types.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+108 -13
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { EncryptedJsonEncoding, Keypair, KeypairType } from '@polkadot/util-crypto/types';
@@ -6,7 +6,7 @@ import type { KeyringPair, KeyringPair$Json, KeyringPair$Meta, SignOptions } fro
import type { PairInfo } from './types.js';
import { objectSpread, u8aConcat, u8aEmpty, u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util';
import { blake2AsU8a, ed25519PairFromSeed as ed25519FromSeed, ed25519Sign, ethereumEncode, keccakAsU8a, keyExtractPath, keyFromPath, secp256k1Compress, secp256k1Expand, secp256k1PairFromSeed as secp256k1FromSeed, secp256k1Sign, signatureVerify, sr25519PairFromSeed as sr25519FromSeed, sr25519Sign, sr25519VrfSign, sr25519VrfVerify } from '@polkadot/util-crypto';
import { blake2AsU8a, dilithiumAccountFromPublic, dilithiumPairFromSeed, dilithiumSign, dilithiumSizes, ed25519PairFromSeed as ed25519FromSeed, ed25519Sign, ethereumEncode, isDilithium, keccakAsU8a, keyExtractPath, keyFromPath, secp256k1Compress, secp256k1Expand, secp256k1PairFromSeed as secp256k1FromSeed, secp256k1Sign, signatureVerify, sr25519PairFromSeed as sr25519FromSeed, sr25519Sign, sr25519VrfSign, sr25519VrfVerify } from '@polkadot/util-crypto';
import { decodePair } from './decode.js';
import { encodePair } from './encode.js';
@@ -20,27 +20,70 @@ interface Setup {
const SIG_TYPE_NONE = new Uint8Array();
const TYPE_FROM_SEED = {
dilithium65: (seed: Uint8Array) => dilithiumPairFromSeed(seed, 'dilithium65'),
dilithium87: (seed: Uint8Array) => dilithiumPairFromSeed(seed, 'dilithium87'),
ecdsa: secp256k1FromSeed,
ed25519: ed25519FromSeed,
ethereum: secp256k1FromSeed,
sr25519: sr25519FromSeed
};
// For the curve types these index Substrate's `MultiSignature`. For the ML-DSA
// types they index the Quantus runtime's `DilithiumSignatureScheme`, where
// `Dilithium87` is variant 0 and `Dilithium65` is variant 1 — a different enum
// that happens to be reached by the same `withType` mechanism, so no special
// casing is needed anywhere upstream of here.
const TYPE_PREFIX = {
dilithium65: new Uint8Array([1]),
dilithium87: new Uint8Array([0]),
ecdsa: new Uint8Array([2]),
ed25519: new Uint8Array([0]),
ethereum: new Uint8Array([2]),
sr25519: new Uint8Array([1])
};
const TYPE_SIGNATURE = {
ecdsa: (m: Uint8Array, p: Partial<Keypair>) => secp256k1Sign(m, p, 'blake2'),
ed25519: ed25519Sign,
ethereum: (m: Uint8Array, p: Partial<Keypair>) => secp256k1Sign(m, p, 'keccak'),
sr25519: sr25519Sign
// The ML-DSA arms take a context; the curve arms ignore the extra argument. The
// signature is written out because TypeScript would otherwise infer the narrowest
// common shape — the two-argument curve arms — and reject passing a context at all.
type SignFn = (message: Uint8Array, pair: Partial<Keypair>, context?: Uint8Array) => Uint8Array;
const TYPE_SIGNATURE: Record<KeypairType, SignFn> = {
dilithium65: (m, p, ctx) => dilithiumSign(m, p, 'dilithium65', requireContext(ctx)),
dilithium87: (m, p, ctx) => dilithiumSign(m, p, 'dilithium87', requireContext(ctx)),
ecdsa: (m, p) => secp256k1Sign(m, p, 'blake2'),
// Wrapped rather than passed directly: ed25519Sign and sr25519Sign take a
// third argument of their own (`onlyJs`), and letting it line up with the
// context slot would silently reinterpret a Uint8Array as a boolean.
ed25519: (m, p) => ed25519Sign(m, p),
ethereum: (m, p) => secp256k1Sign(m, p, 'keccak'),
sr25519: (m, p) => sr25519Sign(m, p)
};
/**
* A post-quantum pair will not sign without being told which context to use.
*
* See `SignOptions.context`. The alternative — defaulting to the extrinsic
* context — produces a valid signature that a spec-147 chain rejects, with
* nothing local able to explain why.
*/
function requireContext (context?: Uint8Array): Uint8Array {
if (!context) {
throw new Error('A signing context is required for post-quantum keypairs; pass options.context (see contextForSpec)');
}
return context;
}
// Every curve arm here is identity or a cheap re-encoding, because on Substrate
// the address *is* the public key. The ML-DSA arms are the exception that the
// rest of this file has to be read in light of: 1952 or 2592 bytes in, 32 out,
// and no way back. `ecdsa` already hints at the shape — it hashes when given an
// uncompressed key — but it stays reversible in the sense that matters, since
// the signature carries a recoverable key. ML-DSA does not, which is why its
// signatures carry the public key explicitly.
const TYPE_ADDRESS = {
dilithium65: dilithiumAccountFromPublic,
dilithium87: dilithiumAccountFromPublic,
ecdsa: (p: Uint8Array) => p.length > 32 ? blake2AsU8a(p) : p,
ed25519: (p: Uint8Array) => p,
ethereum: (p: Uint8Array) => p.length === 20 ? p : keccakAsU8a(secp256k1Expand(p)),
@@ -86,11 +129,21 @@ function vrfHash (proof: Uint8Array, context?: string | Uint8Array, extra?: stri
* an `encoded` property that is assigned with the encoded public key in hex format, and an `encoding`
* property that indicates whether the public key value of the `encoded` property is encoded or not.
*/
export function createPair ({ toSS58, type }: Setup, { publicKey, secretKey }: PairInfo, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null, encTypes?: EncryptedJsonEncoding[]): KeyringPair {
export function createPair ({ toSS58, type }: Setup, { accountId, publicKey, secretKey }: PairInfo, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null, encTypes?: EncryptedJsonEncoding[]): KeyringPair {
const decodePkcs8 = (passphrase?: string, userEncoded?: Uint8Array | null): void => {
const decoded = decodePair(passphrase, userEncoded || encoded, encTypes);
// ML-DSA secrets are 4032/4896 bytes, which is neither of the two lengths
// decodePair would otherwise try. It has to be told.
const decoded = decodePair(passphrase, userEncoded || encoded, encTypes, isDilithium(type)
? dilithiumSizes(type).secretKey
: undefined
);
if (decoded.secretKey.length === 64) {
// Upstream decides "is this a secret key or a seed?" by length, because for
// the curve schemes a 64-byte blob is a secret and a 32-byte one is a seed.
// An ML-DSA secret is neither, and would take the seed branch and be fed to
// keygen as entropy — producing a valid, wrong key in silence. The type
// knows the answer, so ask it.
if (isDilithium(type) || decoded.secretKey.length === 64) {
publicKey = decoded.publicKey;
secretKey = decoded.secretKey;
} else {
@@ -99,6 +152,25 @@ export function createPair ({ toSS58, type }: Setup, { publicKey, secretKey }: P
publicKey = pair.publicKey;
secretKey = pair.secretKey;
}
// The public key has only now arrived. If the pair was constructed from JSON
// it has been reporting an address carried as data since then, so check the
// two agree.
//
// This is not a paranoid check. For every other scheme the address *is* the
// public key, so a JSON file whose `address` field has been edited cannot
// decode at all. Here it decodes perfectly and yields a pair that reports an
// address its key does not control — a user would see the attacker's address
// in their own wallet and believe they held it.
if (accountId) {
const derived = TYPE_ADDRESS[type](publicKey);
if (!u8aEq(derived, accountId)) {
throw new Error('Decoded public key does not match the address in the account JSON');
}
accountId = undefined;
}
};
const recode = (passphrase?: string): Uint8Array => {
@@ -110,8 +182,15 @@ export function createPair ({ toSS58, type }: Setup, { publicKey, secretKey }: P
return encoded;
};
// While an ML-DSA pair restored from JSON is locked, its public key is still
// inside the encrypted blob and the account id cannot be computed — so use the
// one carried alongside. `decodePkcs8` clears it once the real key arrives and
// has been checked against it.
const addressRawOf = (): Uint8Array =>
accountId ?? TYPE_ADDRESS[type](publicKey);
const encodeAddress = (): string => {
const raw = TYPE_ADDRESS[type](publicKey);
const raw = addressRawOf();
return type === 'ethereum'
? ethereumEncode(raw)
@@ -123,7 +202,7 @@ export function createPair ({ toSS58, type }: Setup, { publicKey, secretKey }: P
return encodeAddress();
},
get addressRaw (): Uint8Array {
const raw = TYPE_ADDRESS[type](publicKey);
const raw = addressRawOf();
return type === 'ethereum'
? raw.slice(-20)
@@ -146,6 +225,13 @@ export function createPair ({ toSS58, type }: Setup, { publicKey, secretKey }: P
derive: (suri: string, meta?: KeyringPair$Meta): KeyringPair => {
if (type === 'ethereum') {
throw new Error('Unable to derive on this keypair');
} else if (isDilithium(type)) {
// Not "not implemented" — not possible. A child here would have to come
// from this pair's key material, and ML-DSA keys are not derivable from
// one another at all; the Quantus tree derives every account from the
// mnemonic independently. So the caller needs the mnemonic, not this
// pair, and saying that is more use than a generic refusal.
throw new Error(`Unable to derive from an existing ${type} pair; derive from the mnemonic with createFromUri instead`);
} else if (isLocked(secretKey)) {
throw new Error('Cannot derive on a locked keypair');
}
@@ -173,7 +259,7 @@ export function createPair ({ toSS58, type }: Setup, { publicKey, secretKey }: P
options.withType
? TYPE_PREFIX[type]
: SIG_TYPE_NONE,
TYPE_SIGNATURE[type](u8aToU8a(message), { publicKey, secretKey })
TYPE_SIGNATURE[type](u8aToU8a(message), { publicKey, secretKey }, options.context)
);
},
toJson: (passphrase?: string): KeyringPair$Json => {
@@ -203,6 +289,15 @@ export function createPair ({ toSS58, type }: Setup, { publicKey, secretKey }: P
return sr25519VrfSign(message, { secretKey }, context, extra);
}
// There is no ML-DSA VRF. The construction below fakes one out of an
// ordinary signature, which is sound only because the underlying schemes
// are deterministic and unique — properties ML-DSA's signature does not
// have in the form this needs. Refuse rather than produce something that
// looks like a VRF output and is not verifiable as one.
if (isDilithium(type)) {
throw new Error(`VRF signing is not available for ${type}`);
}
const proof = TYPE_SIGNATURE[type](u8aToU8a(message), { publicKey, secretKey });
return u8aConcat(vrfHash(proof, context, extra), proof);
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KeyringPair, KeyringPair$Json, KeyringPair$Meta } from '../types.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KeypairType } from '@polkadot/util-crypto/types';
+16 -1
View File
@@ -1,7 +1,22 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
export interface PairInfo {
/**
* The 32-byte account id, when it cannot be derived from `publicKey`.
*
* Only ML-DSA pairs need this, and only while locked. Everywhere else the
* address is a cheap function of the public key, so a pair restored from JSON
* can report its address before anyone supplies a password. A Quantus account
* id is a one-way Poseidon2 hash, and the public key lives inside the
* encrypted blob — so between `createFromJson` and `decodePkcs8` there is
* nothing to compute it from, and callers read `pair.address` in exactly that
* window.
*
* Carried as data rather than derived, and checked against the real public key
* once that arrives.
*/
accountId?: Uint8Array | undefined;
publicKey: Uint8Array;
secretKey?: Uint8Array | undefined;
seed?: Uint8Array | null;
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KeyringPair, KeyringPairs } from './types.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '@polkadot/util/types';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KeypairType } from '@polkadot/util-crypto/types';
+15 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '@polkadot/util/types';
@@ -72,6 +72,17 @@ export interface KeyringPair$Json extends EncryptedJson {
}
export interface SignOptions {
/**
* The FIPS 204 domain-separation context, for post-quantum pairs.
*
* Mandatory for `dilithium65`/`dilithium87` and ignored by every other type.
* There is deliberately no default: Quantus extrinsics on spec >= 148 are
* verified under `QUANTUS_EXTRINSIC` and earlier ones under the empty context,
* and a signature made under the wrong one is cryptographically valid, rejected
* by the chain, and locally indistinguishable from a correct one. Only the
* caller knows the spec version, so only the caller can answer.
*/
context?: Uint8Array;
/** Create a MultiSignature-compatible output with an indicator type */
withType?: boolean;
}
@@ -116,13 +127,13 @@ export interface KeyringInstance {
addPair (pair: KeyringPair): KeyringPair;
addFromAddress (address: string | Uint8Array, meta?: KeyringPair$Meta, encoded?: Uint8Array | null, type?: KeypairType, ignoreChecksum?: boolean): KeyringPair;
addFromJson (pair: KeyringPair$Json, ignoreChecksum?: boolean): KeyringPair;
addFromMnemonic (mnemonic: string, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair;
addFromMnemonic (mnemonic: string, meta?: KeyringPair$Meta, type?: KeypairType, wordlist?: string[]): KeyringPair;
addFromPair (pair: Keypair, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair
addFromSeed (seed: Uint8Array, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair;
addFromUri (suri: string, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair;
addFromUri (suri: string, meta?: KeyringPair$Meta, type?: KeypairType, wordlist?: string[]): KeyringPair;
createFromJson (json: KeyringPair$Json, ignoreChecksum?: boolean): KeyringPair;
createFromPair (pair: Keypair, meta: KeyringPair$Meta, type: KeypairType): KeyringPair
createFromUri (suri: string, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair;
createFromUri (suri: string, meta?: KeyringPair$Meta, type?: KeypairType, wordlist?: string[]): KeyringPair;
getPair (address: string | Uint8Array): KeyringPair;
getPairs (): KeyringPair[];
getPublicKeys (): Uint8Array[];
+3 -3
View File
@@ -15,14 +15,14 @@
},
"sideEffects": false,
"type": "module",
"version": "13.2.3",
"version": "14.0.3-quantus.2",
"main": "index.js",
"dependencies": {
"@polkadot/util": "13.2.3",
"@polkadot/util": "14.0.3",
"@substrate/ss58-registry": "^1.51.0",
"tslib": "^2.8.0"
},
"devDependencies": {
"@polkadot/hw-ledger": "13.2.3"
"@polkadot/hw-ledger": "14.0.3"
}
}
+8 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
@@ -22,10 +22,15 @@ describe('defaults', (): void => {
).toEqual([]);
});
it('has no entries for testnets', (): void => {
// Upstream asserted the opposite: that no testnet appears in knownGenesis.
// This fork carries Quantus testnets deliberately — a wallet for a chain
// under development has to reach its testnet — so the assertion becomes that
// any testnet listed here is one we meant to list. See defaults/genesis.ts.
it('has genesis entries only for testnets we deliberately support', (): void => {
expect(
genesisKeys.filter((network) =>
knownTestnet[network]
knownTestnet[network] &&
!network.startsWith('quantus')
)
).toEqual([]);
});
+41 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KnownGenesis } from '../types.js';
@@ -8,9 +8,20 @@ import type { KnownGenesis } from '../types.js';
// oldest goes last. This make lookups for the current a simple genesisHash[0]
// where the latest ios always the first entry (See Kusama as an example)
//
// IMPORTANT: Apart from the test relays, this list is limited to live parachains
// and live production networks. It does not and should not contain any testnets,
// either stand-alone or connected to test relays such as Westend/Rococo
// UPSTREAM SAID: "Apart from the test relays, this list is limited to live
// parachains and live production networks. It does not and should not contain
// any testnets, either stand-alone or connected to test relays such as
// Westend/Rococo"
//
// THIS FORK DOES NOT. That rule keeps testnets out of polkadot-js/apps' chain
// picker, which is a sensible product decision for a production explorer and the
// wrong one for us. Quantus is a chain under active development, and a wallet
// that cannot talk to its testnet is a wallet that cannot be tested against
// anything but real funds. Testnets are first-class here.
//
// `isTestnet` is still set honestly, so a consumer that wants to hide them can;
// what changed is that we no longer make that choice on the consumer's behalf.
// See `toExpanded` in ../interfaces.ts.
export const knownGenesis: KnownGenesis = {
acala: [
'0xfc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c'
@@ -46,9 +57,17 @@ export const knownGenesis: KnownGenesis = {
composable: [
'0xdaab8df776eb52ec604a5df5d388bb62a050a0aaec4556a64265b9d42755552d'
],
creditcoin3: [
'0x4436a7d64e363df85e065a894721002a86643283f9707338bf195d360ba2ee71', // cc3 mainnet
'0xfc4ec97a1c1f119c4353aecb4a17c7c0cf7b40d5d660143d8bad9117e9866572', // cc3 testnet/drynet
'0xfc9df99a665f964aed6649f275055e54df5e3420489538ed31d7788f53d11ef6' // cc3 devnet
],
darwinia: [
'0xe71578b37a7c799b0ab4ee87ffa6f059a6b98f71f06fb8c84a8d88013a548ad6'
],
dentnet: [
'0x0313f6a011d128d22f996703cbab05162e2fdc9e031493314fe6db79979c5ca7'
],
'dock-mainnet': [
'0x6bfe24dca2a3be10f22212678ac13a6446ec764103c0f3471c71609eac384aae',
'0xf73467c6544aa68df2ee546b135f955c46b90fa627e9b5d7935f41061bb8a5a9'
@@ -99,9 +118,15 @@ export const knownGenesis: KnownGenesis = {
'0xe3777fa922cafbff200cadeaea1a76bd7898ad5b89f7848999058b50e715f636', // Kusama CC2
'0x3fd7b9eb6a00376e5be61f01abb429ffb0b104be05eaff4d458da48fcd425baf' // Kusama CC1
],
liberland: [
'0x6bd89e052d67a45bb60a9a23e8581053d5e0d619f15cb9865946937e690c42d6'
],
matrixchain: [
'0x3af4ff48ec76d2efc8476730f423ac07e25ad48f5f4c9dc39c778b164d808615'
],
mythos: [
'0xf6ee56e9c5277df5b4ce6ae9983ee88f3cbed27d31beeb98f9f84f997a1ab0b9'
],
nodle: [
'0x97da7ede98d7bad4e36b4d734b6055425a3be036da2a332ea5a7037656427a21'
],
@@ -139,6 +164,18 @@ export const knownGenesis: KnownGenesis = {
polymesh: [
'0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063'
],
// Quantus mainnet, read from the node with `chain_getBlockHash(0)` on
// 2026-09-09, the day it launched. Not the staging chain, which is
// `0xb0e90ed9f291a9194f79…` and which telemetry confusingly calls
// "Quantus Staging Mainnet".
quantus: [
'0xfb5487c0be6ae4ade2d41d16e50465129861636c2b8d61fa94d7a19631626fba'
],
// Heisenberg, the public Quantus testnet. A testnet carrying a genesis hash is
// a deliberate departure from upstream — see the note above.
'quantus-heisenberg': [
'0xa5aa9e5c84d4a3722c152295e7973c9af522f2fb1ef7db5afaa3d5f4dc8d3b4f'
],
quartz: [
'0xcd4d732201ebe5d6b014edda071c4203e16867305332301dc8d092044b28e554'
],
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KnownIcon } from '../types.js';
+3 -2
View File
@@ -1,7 +1,8 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { knownGenesis } from './genesis.js';
export { knownIcon } from './icons.js';
export { knownLedger } from './ledger.js';
export { knownTestnet } from './testnets.js';
export { knownQuantus } from './quantus.js';
export { exposedTestnet, knownTestnet } from './testnets.js';
+5 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KnownLedger } from '../types.js';
@@ -17,7 +17,9 @@ export const knownLedger: KnownLedger = {
bittensor: 0x00000162,
centrifuge: 0x000002eb,
composable: 0x00000162,
creditcoin3: 0x00000162,
darwinia: 0x00000162,
dentnet: 0x000002de,
'dock-mainnet': 0x00000252,
edgeware: 0x0000020b,
encointer: 0x000001b2,
@@ -31,7 +33,9 @@ export const knownLedger: KnownLedger = {
karura: 0x000002ae,
khala: 0x000001b2,
kusama: 0x000001b2,
liberland: 0x000002ff,
matrixchain: 0x00000483,
mythos: 0x0000003c,
nodle: 0x000003eb,
origintrail: 0x00000162,
parallel: 0x00000162,
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KnownSubstrate } from '../types.js';
// Quantus networks, which `@substrate/ss58-registry` does not carry.
//
// Prefix 189 is unclaimed in that registry — nothing occupies 185..195 at all —
// so this is an addition rather than an override. It lives in this fork because
// the registry is Parity-curated and post-quantum support has not landed
// upstream; see the standing convention in quantus/extension#1.
//
// `standardAccount: 'ml-dsa'` is a value upstream's type does not allow, which
// is why `KnownSubstrate` is widened in ../types.ts. It is load-bearing: a null
// or missing `standardAccount` makes `toExpanded` mark the network `isIgnored`
// and it disappears from `availableNetworks`.
export const knownQuantus: KnownSubstrate[] = [
{
decimals: [12],
displayName: 'Quantus',
network: 'quantus',
prefix: 189,
standardAccount: 'ml-dsa',
symbols: ['QTC'],
website: 'https://quantus.com'
},
{
decimals: [12],
displayName: 'Quantus Heisenberg',
network: 'quantus-heisenberg',
prefix: 189,
standardAccount: 'ml-dsa',
// HEI, not QTC — confirmed from the chain itself via system_properties on
// a1-heisenberg.quantus.cat. A testnet token that displayed as the mainnet
// symbol would be a good way to confuse play money for real.
symbols: ['HEI'],
website: 'https://quantus.com'
}
];
+21 -2
View File
@@ -1,9 +1,10 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KnownTestnet } from '../types.js';
// testnets should not allow selection
// Marks a network as a testnet. Purely descriptive — see `exposedTestnet` below
// for which of them this fork actually offers.
export const knownTestnet: KnownTestnet = {
'': true, // this is the default non-network entry
'cess-testnet': true,
@@ -11,6 +12,24 @@ export const knownTestnet: KnownTestnet = {
jupiter: true,
'mathchain-testnet': true,
p3dt: true,
'quantus-heisenberg': true,
subspace_testnet: true,
'zero-alphaville': true
};
// Testnets this fork exposes anyway, despite upstream's rule that a testnet is
// never offered.
//
// That rule keeps testnets out of polkadot-js/apps' chain picker, which is right
// for a production explorer and wrong for us: Quantus is under active
// development, and a wallet that cannot reach its testnet can only be tested
// against real funds.
//
// An explicit allowlist rather than dropping the rule wholesale. Dropping it
// exposed all fifteen of upstream's testnets too, which is a change we have no
// reason to make and no way to have thought about — it moved the tail of the
// sorted list from 'ZERO' to 'ZERO Alphaville' and would have surprised anyone
// reading `availableNetworks`. Opting in one network at a time says what we mean.
export const exposedTestnet: KnownTestnet = {
'quantus-heisenberg': true
};
+39 -9
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
@@ -6,7 +6,7 @@
import type { SubstrateNetwork } from './types.js';
import { knownGenesis, knownIcon, knownLedger, knownTestnet } from './defaults/index.js';
import { allNetworks, availableNetworks, selectableNetworks } from './index.js';
import { allNetworks, availableNetworks, isQuantumSafe, selectableNetworks } from './index.js';
describe('availableNetworks', (): void => {
it('has the correct starting order', (): void => {
@@ -51,16 +51,40 @@ describe('availableNetworks', (): void => {
).toEqual([]);
});
it('has no testnets exposed', (): void => {
// Upstream asserted that no testnet reaches availableNetworks at all. This
// fork exposes the Quantus ones deliberately — see exposedTestnet — so what is
// worth pinning is that an exposed testnet is still *labelled* one. Losing the
// label is the failure that matters: a user cannot tell play money from real
// money by looking at an address.
it('labels every exposed testnet as a testnet', (): void => {
expect(
Object.keys(knownTestnet).filter((network) =>
availableNetworks.some((a) =>
a.network === network
)
availableNetworks.some((a) => a.network === network && !a.isTestnet)
)
).toEqual([]);
});
it('exposes no testnets except the ones we opted into', (): void => {
expect(
availableNetworks
.filter((a) => a.isTestnet && !a.network.startsWith('quantus'))
.map((a) => a.network)
).toEqual([]);
});
// These lists stay complete on purpose. An earlier version of this fork
// filtered them to post-quantum chains, which broke @polkadot/types-known — it
// looks up every chain it holds upgrade history for and throws when one is
// missing, so @polkadot/api failed at import. Gating what a wallet offers is
// the wallet's job; isQuantumSafe is exported for it and the extension applies
// it in its own chain list.
it('identifies which chains are post-quantum', (): void => {
expect(isQuantumSafe('ml-dsa')).toEqual(true);
expect(isQuantumSafe('Sr25519')).toEqual(false);
expect(isQuantumSafe(null)).toEqual(false);
expect(availableNetworks.filter((n) => isQuantumSafe(n.standardAccount)).map((n) => n.network)).toEqual(['quantus', 'quantus-heisenberg']);
});
it('has allNetworks icons, except for overrides', (): void => {
expect(
availableNetworks.filter(({ icon, network }) =>
@@ -92,14 +116,20 @@ describe('availableNetworks', (): void => {
});
describe('allNetworks', (): void => {
it('has no ss58 duplicates', (): void => {
// Upstream asserted one network per SS58 prefix. That stops holding the moment
// a chain has a testnet: Quantus mainnet and Heisenberg are both 189, because
// they are the same chain at two deployments and an address is valid on
// either. Consumers key on genesisHash, so sharing a prefix costs nothing.
it('has no ss58 duplicates, except deployments of the same chain', (): void => {
const dupes: SubstrateNetwork[] = [];
const uniques: SubstrateNetwork[] = [];
allNetworks.forEach((a): void => {
if (uniques.some((u) => u.prefix === a.prefix)) {
const clash = uniques.find((u) => u.prefix === a.prefix);
if (clash && clash.network.split('-')[0] !== a.network.split('-')[0]) {
dupes.push(a);
} else {
} else if (!clash) {
uniques.push(a);
}
});
+2 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
// TODO: This was removed, really cannot recall the reason...
@@ -7,3 +7,4 @@
export * from './interfaces.js';
export { packageInfo } from './packageInfo.js';
export { isQuantumSafe } from './quantumSafe.js';
+35 -6
View File
@@ -1,11 +1,11 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KnownSubstrate, Network, SubstrateNetwork } from './types.js';
import knownSubstrate from '@substrate/ss58-registry';
import { knownGenesis, knownIcon, knownLedger, knownTestnet } from './defaults/index.js';
import { exposedTestnet, knownGenesis, knownIcon, knownLedger, knownQuantus, knownTestnet } from './defaults/index.js';
// These are known prefixes that are not sorted
const UNSORTED = [0, 2, 42];
@@ -26,7 +26,16 @@ function toExpanded (o: KnownSubstrate): SubstrateNetwork {
// filtering
n.isTestnet = !!knownTestnet[network] || TESTNETS.includes(nameParts[nameParts.length - 1]);
n.isIgnored = n.isTestnet || (
// Upstream had a bare `isTestnet ||` here, so a testnet was dropped no matter
// how complete its entry was. That suits a production chain picker and does
// not suit us — Quantus development happens on Heisenberg, and tools that
// cannot reach a testnet can only be tested against real funds.
//
// The exception is an allowlist rather than a blanket removal: see
// `exposedTestnet`. `isTestnet` is still reported honestly either way, so a
// consumer can label or filter — we simply no longer make that call for them.
n.isIgnored = (n.isTestnet && !exposedTestnet[network]) || (
!(
o.standardAccount &&
o.decimals?.length &&
@@ -59,11 +68,31 @@ function sortNetworks (a: Network, b: Network): number {
: 1;
}
// This is all the Substrate networks with our additional information
export const allNetworks = knownSubstrate.map(toExpanded);
// This is all the Substrate networks with our additional information.
//
// None of these three lists is filtered to post-quantum chains, and an earlier
// version of this fork filtering `availableNetworks` was a mistake worth
// recording. These are *library* lists, not a product's offer: other packages
// consume them expecting the chains Substrate knows about to be present.
// `@polkadot/types-known` looks up every chain it has upgrade history for and
// **throws** when one is missing, so removing Polkadot and Kusama here made
// `@polkadot/api` fail at import — nothing to do with Quantus, and impossible to
// diagnose from the error.
//
// Gating what a wallet *offers* is the wallet's decision. `isQuantumSafe` is
// exported for exactly that, and the extension applies it in its own chain list.
// Keeping these complete also means a pasted Polkadot address still decodes and
// renders as what it is — recognisable, and therefore refusable.
//
// Quantus is appended rather than merged: `@substrate/ss58-registry` does not
// carry it and prefix 189 is unclaimed there, so there is nothing to override.
// Appending also means a registry bump cannot silently drop it.
export const allNetworks = knownSubstrate
.map((o) => toExpanded(o as KnownSubstrate))
.concat(knownQuantus.map(toExpanded));
// The list of available/claimed prefixes
// - no testnets
// - testnets only where explicitly exposed (see exposedTestnet)
// - we only include those where we have a standardAccount
// - sort by name, however we keep 0, 2, 42 first in the list
export const availableNetworks = allNetworks.filter(filterAvailable).sort(sortNetworks);
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './index.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @polkadot/dev
export const packageInfo = { name: '@polkadot/networks', path: 'auto', type: 'auto', version: '13.2.3' };
export const packageInfo = { name: '@polkadot/networks', path: 'auto', type: 'auto', version: '14.0.3-quantus.2' };
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KnownSubstrate } from './types.js';
/**
* The `standardAccount` values that survive a cryptographically relevant quantum
* computer.
*
* Everything else in the registry `*25519`, `Ed25519`, `Sr25519`, `secp256k1`
* is a discrete-log scheme, and Shor's algorithm breaks all of them at once. A
* chain using one of those has no quantum-safe account type to offer, whatever
* else is true of it.
*/
const QUANTUM_SAFE_ACCOUNTS: KnownSubstrate['standardAccount'][] = ['ml-dsa'];
/**
* Whether a network's accounts are post-quantum.
*
* This gates what the tooling built on this fork will *offer*. It deliberately
* does not gate address encoding or decoding: pasting a Polkadot address into a
* Quantus wallet should render as a Polkadot address so it can be recognised and
* refused, not fail to decode and look like a typo.
*
* A `null` standardAccount which the registry uses for chains that never said
* is treated as unsafe. Unknown is not the same as safe, and the cost of being
* wrong in that direction is a user holding funds under a key Shor's algorithm
* recovers.
*/
export function isQuantumSafe (standardAccount: KnownSubstrate['standardAccount']): boolean {
return QUANTUM_SAFE_ACCOUNTS.includes(standardAccount);
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/networks authors & contributors
// Copyright 2017-2026 @polkadot/networks authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+14 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/keyring authors & contributors
// Copyright 2017-2026 @polkadot/keyring authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { RegistryEntry } from '@substrate/ss58-registry';
@@ -12,7 +12,19 @@ export type KnownLedger = Record<string, number>;
export type KnownGenesis = Record<string, HexString[]>;
export type KnownSubstrate = RegistryEntry;
/**
* A registry entry, widened for schemes the upstream registry does not know about.
*
* `@substrate/ss58-registry` types `standardAccount` as a closed union of the
* curves Substrate ships with. Quantus signs with ML-DSA, which is not one of
* them and given the registry is a Parity-curated list and post-quantum
* support has not landed upstream is not going to become one. Widening here
* keeps the Quantus entry in `defaults/quantus.ts` type-checked rather than
* cast, and leaves every upstream entry exactly as the registry declares it.
*/
export type KnownSubstrate = Omit<RegistryEntry, 'standardAccount'> & {
standardAccount: RegistryEntry['standardAccount'] | 'ml-dsa';
};
export type KnownTestnet = Record<string, true>;
+10 -8
View File
@@ -20,7 +20,7 @@
"./packageDetect.cjs"
],
"type": "module",
"version": "13.2.3",
"version": "14.0.3-quantus.2",
"browser": {
"crypto": false,
"stream": false
@@ -29,16 +29,18 @@
"dependencies": {
"@noble/curves": "^1.3.0",
"@noble/hashes": "^1.3.3",
"@polkadot/networks": "13.2.3",
"@polkadot/util": "13.2.3",
"@polkadot/wasm-crypto": "^7.4.1",
"@polkadot/wasm-util": "^7.4.1",
"@polkadot/x-bigint": "13.2.3",
"@polkadot/x-randomvalues": "13.2.3",
"@polkadot/networks": "14.0.3-quantus.2",
"@polkadot/util": "14.0.3",
"@polkadot/wasm-crypto": "^7.5.3",
"@polkadot/wasm-util": "^7.5.3",
"@polkadot/x-bigint": "14.0.3",
"@polkadot/x-randomvalues": "14.0.3",
"@quantus/crypto": "^0.1.0",
"@scure/base": "^1.1.7",
"@scure/sr25519": "^0.2.0",
"tslib": "^2.8.0"
},
"peerDependencies": {
"@polkadot/util": "13.2.3"
"@polkadot/util": "14.0.3"
}
}
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { decodeAddress } from './decode.js';
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { sshash } from './sshash.js';
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { availableNetworks } from '../networks.js';
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveJunction } from '../key/DeriveJunction.js';
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+5 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
@@ -15,7 +15,7 @@ export function encodeAddress (key: string | Uint8Array, ss58Format: Prefix = de
// decode it, this means we can re-encode an address
const u8a = decodeAddress(key);
if ((ss58Format < 0) || (ss58Format > 16383) || [46, 47].includes(ss58Format)) {
if ((ss58Format < 0) || (ss58Format > 16383 && !ss58Exceptions.includes(ss58Format)) || [46, 47].includes(ss58Format)) {
throw new Error('Out of range ss58Format specified');
} else if (!defaults.allowedDecodedLengths.includes(u8a.length)) {
throw new Error(`Expected a valid key to convert, with length ${defaults.allowedDecodedLengths.join(', ')}`);
@@ -38,3 +38,6 @@ export function encodeAddress (key: string | Uint8Array, ss58Format: Prefix = de
)
);
}
// Exceptions like 29972 (Mythos chain) which uses Ethereum style account (not ss58 Encoded)
const ss58Exceptions = [29972];
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { u8aEq } from '@polkadot/util';
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
@@ -1,4 +1,4 @@
// Copyright 2017-2024 @polkadot/util-crypto authors & contributors
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HashType } from '../secp256k1/types.js';

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