The Quantova post quantum client core, written in Rust. It is the one place a Quantova client does the four things it has to get exactly right. It derives a post quantum key. It builds a transaction. It signs that transaction with the post quantum scheme. It speaks the Quantova gateway wire. QCore.js and QCore.py are generated over this same core, so a signature is written once in Rust and never rewritten in another language. A second implementation of the signing would be a second chance to be wrong with a user's money, so there is only ever the one.
One core with one binding for each language. QCore.rs is this Rust core. QCore.js is the JavaScript and WebAssembly binding over it. QCore.py is the Python binding over it. All three derive the same address from the same seed and sign the same body to the same bytes, because they are the one core rather than three copies of it.
Any program that holds a Quantova account, reads the chain, and sends signed transactions. A wallet, a block explorer, a faucet, a trading bot, a backend service, or a command line tool. The program makes whatever network calls it prefers and QCore does the cryptography and the wire, so the program never hand builds a signature or a request body and never has to understand the byte layout of a transaction.
Quantova is post quantum from the ground. There is no elliptic curve anywhere in it, no secp256k1, no ECDSA, no Ethereum address, and no Substrate envelope. Every primitive below is Quantova's own from scratch implementation in the Q Crypto library, carrying no third party cryptography dependency, and QCore builds directly on the chain's own account and transaction code so a client and the node agree byte for byte.
-
Key derivation. A wallet holds one master seed of thirty two bytes. QCore grows a per account seed from it with SHAKE256 over the master seed, the scheme byte, and the account index, then derives a module lattice key pair from that seed. The node runs the identical derivation, so the account a key signs from is the account that holds the funds.
-
The signing scheme. The default is the module lattice signature ML-DSA-65 as standardised in FIPS 204, carried on the wire as scheme one. The hash based signature SLH-DSA as standardised in FIPS 205 is scheme two. Signing is deterministic, so one transaction body always signs to one exact run of bytes, which makes a signed transaction reproducible and testable.
-
The address. A Quantova address is the Bech32m Q1 string that renders the SHA3 256 hash of the scheme byte together with the whole module lattice public key of one thousand nine hundred fifty two bytes. The entire public key is bound into the address. Nothing is truncated to a twenty byte hash and there is no key recovery from a signature, so one address names exactly one post quantum key.
-
The transaction. A transaction body carries the sender, the nonce, the meter limit, the fee, and the call. The bytes a signature stands over are the SHA3 256 hash of the canonical body followed by a fixed Quantova transaction domain tag, so a transaction signature can never be replayed as another kind of signed message. QCore assembles the body, signs the digest with the account's post quantum key, and returns the canonical wrapper bytes and the transaction id ready for the gateway.
Quantova shares no wire, no address, and no unit with any other chain, and QCore speaks only Quantova. The address is a Q1 Bech32m string over a full post quantum key, never a hex twenty byte address and never an SS58 string. Money is counted in Quon, the smallest unit, at one million Quon to one QTOV, and it always crosses the wire as a decimal string so a JavaScript number never rounds a balance. The wire is the Quantova gateway, an HTTP POST to a named method under a version prefix with a flat JSON body, not Ethereum JSON RPC and not a Substrate WebSocket. The transaction encoding is Quantova's own canonical codec, not RLP and not SCALE. The signatures come from Q Crypto, Quantova's own implementation of the lattice and hash standards written from scratch, not a borrowed crate. QCore does not translate to or from any older format because there is nothing older to translate.
Under the client feature the core generates a fresh master seed from the operating system random source, so a wallet creates a new key with one call. The recovery phrase is the only backup and stays on the device.
let seed = qcore::generate_seed()?;
let phrase = qcore::mnemonic_from_seed(&seed);The client feature adds a small HTTP transport over the standard library for native callers and for proving the core against a running gateway. The example below reads the fee and the nonce, signs a transfer inside the core, and submits it. The caller passes the highest fee it will accept and the core refuses to sign a fee the gateway reports above it, so a gateway cannot inflate the fee and drain the account. Over this transport the client speaks plaintext only to a loopback node, since without transport security a gateway across an untrusted network could rewrite the fee and the nonce, so reach a remote gateway through a tunnel that ends on loopback. The testnet network names the same https endpoint in every client, and the built in transport has no TLS, so Client::for_network refuses it and Client::for_network_with_transport opens it over any type that implements Transport, for example one built on a TLS library the application already trusts.
use qcore::{Client, account_address, TxStatus};
let client = Client::new("http://127.0.0.1:8645");
let seed = [11u8; 32];
let to = account_address(&seed, 1);
let info = client.node_info()?;
let (signed, outcome) = client.transfer(&seed, 0, &to, 1000, info.transfer_fee)?;
let status = client.transaction(&signed.tx_id)?;A gateway is not trusted, so the client checks what it reports before it signs. The head height is bounded by the time since the genesis of the named network at the fastest block rate the chain is built for, and the genesis time of each network is one named constant in the core, TESTNET_GENESIS_SECS, that the JavaScript and Python clients read through their bindings. A transaction stays valid for three hundred blocks past the head, and a head that falls back or leaps faster than blocks are made is refused.
Each client keeps a signing session. A nonce that was signed once is never signed again with different bytes or with the same bytes until the caller names that nonce, which is how a caller says the first transaction never landed. A gateway that rejects a transaction and then reports a later nonce is refused for the same reason, since the first transaction may still be valid. An order nonce that the gateway raises above the one the session tracked is refused unless the caller names it, because an order stays valid until the contract reaches its nonce. The session lives in memory, so a new process starts with no holds and should name the nonce it expects.
The session also remembers the sending accounts and every target signed for, together with an optional address book passed to Client::with_address_book. A target that shares at least two leading and two trailing characters with a known address, eight characters in all, but is a different address is refused as a lookalike.
A new session does not take an order nonce above zero from the gateway alone. Name the expected order nonce, or save the session with Client::save_session and restore it with Client::restore_session so the tracked nonces and held signatures survive a restart. The client refuses to sign for a retired chain name such as Q-test-net-1.
Every Client in a process shares one signing session for each chain and account, and that session can be saved with save_session and restored with restore_session, so two clients or two runs never sign two transactions at one nonce. A nonce the gateway reports below the one the session already reached is refused unless you name it with an expected nonce. A signing reserves its nonce until the transaction is signed, and a signing that stops early frees only the nonce it reserved. The free signing functions such as sign_transfer take the chain head and then the genesis hash as their last two arguments. They refuse a validity deadline that is not past the head or lies more than 3600 blocks beyond it, so a signed transaction can never stay valid for long. Every signature covers the genesis hash, so a transaction signed for one network never verifies on another network that reuses its name. The client signs for a public network only with the genesis hash pinned in its Network, and refuses when the gateway reports a different one or when no genesis is pinned. A mainnet client also signs only once it holds a trusted validator roster.
verify_finality measures signed stake against the active set of the roster you pass, the validators that hold at least the minimum self stake, up to the largest active set by stake, and verifies only when they hold strictly more than two thirds of that stake. A roster that gives two validators one attestation key is refused. verify_finality_in_epoch also checks that the header height lies in the epoch of the roster, using the epoch length of the network, and the client always chooses the roster that way, never from the epoch the gateway names. Each roster entry names the validator id, its stake, its attestation key and its sortition root with the number of slots the root covers, exactly as get_validator_set serves them, and every signer must prove its committee membership against its root.
Client::confirm_inclusion proves a transaction is inside a finalized block without trusting the gateway. It fetches the inclusion proof and the finality certificate of the block, checks the certificate against a validator roster and genesis hash you supply and trust, and checks the transaction and its position against that block. The chain includes and charges a contract call even when it reverts, so Client::confirm_transaction confirms only a transfer or a key registration and refuses a contract call; confirm a call with Client::confirm_call_event, which proves the event the contract emits in that block. Client::transaction reports a status checked against a trusted roster as finalised, reports it as unverified when no roster is held, and refuses an unverified status on mainnet.
Client::trust_roster gives the client a genesis hash and a validator roster you trust. Every attestation signs the genesis hash, so a certificate from another network that reuses the chain name and keys never verifies. From then on the client checks the head the gateway reports against the finality certificate of that block before it signs, so a gateway cannot move a deadline by lying about the head. A public network client refuses to sign until it has a trusted roster. On a private chain without a roster, give the network its genesis time and block rate with Network::with_clock or Client::with_chain_clock, so the head is bounded by the chain clock; with neither the client refuses to sign.
Numbers the gateway reports are read strictly. An amount or fee string carries only the ASCII digits zero to nine, a count is a JSON integer, and a response that is not strict JSON in UTF-8 is refused. A recovery phrase can be read and written as a byte buffer that the caller wipes, with mnemonic_bytes_from_seed and seed_from_mnemonic_bytes.
cargo build --features client
cargo test --features client
The client feature also builds a small terminal client, qcore, over the same core, so a person can create a wallet, read the chain, and send a transfer from the shell. It signs exactly as a wallet does and never hand rolls a signature.
qcore new create a wallet, seed, phrase, and address
qcore address <seed-hex> [index] the address for a seed and index
qcore info <gateway-url> the chain id, height, and fee
qcore register <gateway-url> <seed-hex> register a funded account's key so it can send
qcore balance <gateway-url> <address> an account balance and nonce
qcore send <gateway-url> <seed-hex> <to> <amount> <max-fee> sign and submit a transfer
qcore status <gateway-url> <tx-id> where a transaction is
QCore.rs is built and owned by Quantova Inc and is the reference core that QCore.js and QCore.py are generated over. It carries none of the industry stack and inherits nothing from it. Everything from the key derivation to the wire is Quantova's own. It is released under the Apache 2.0 and MIT licenses so any wallet, explorer, or service may build on it, and the copyright stays with Quantova Inc.