The Quantova post quantum client core for Python. It is the Rust core QCore.rs built as a native extension with pyo3, so the key derivation, the post quantum signing, and every RPC request body are the core's and are never rewritten in Python. This package does the HTTP and reads the documented response fields, and the core does everything that has to be exactly right. 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 the Rust core. QCore.js is the JavaScript binding over it. QCore.py is this Python binding over it. It imports as quantova_qcore, a name distinct from the unrelated qcore project, and the distribution is quantova-qcore. It is not published on PyPI, and the project named qcore on PyPI is an unrelated package, so never install it with pip install qcore. Earlier releases imported as qcore, and that name is gone so it can never be confused with the unrelated package. Build it from this repository with maturin as described under Building.
Any Python program that holds a Quantova account, reads the chain, and sends signed transactions. A backend service, an indexer, an explorer, a faucet, a validator tool, or a script. Your code drives the flow it wants and QCore.py derives the key, signs the transaction, and builds the request body, so nothing about the cryptography or the byte layout of a transaction is written in Python.
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, compiled into the native extension that ships with this package.
-
Key derivation. Your program holds one master seed of thirty two bytes. QCore.py 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. A call may also carry a value in Quon and a chain id, so a signature can pay a call and can never be replayed onto another network. The bytes a signature stands over are the SHA3 256 hash of the canonical body, and a fixed Quantova transaction domain tag is bound in as the post quantum signing context, so a transaction signature can never be replayed as another kind of signed message. Every address the body carries is bound in as the raw thirty two byte payload underneath the Q1 string, never as the string itself, so two spellings of the same address always sign to the same bytes. QCore.py assembles the body, signs the digest inside the native extension, and returns the 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.py 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 comes back as a decimal string that you convert with int only where a whole number is needed. 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 library.
import quantova_qcore as qcore
seed = qcore.generate_seed() # thirty two random bytes in a bytearray from the platform source
phrase = qcore.mnemonic_from_seed(seed) # the only backup as UTF-8 bytes in a bytearray you can wipeqcore.seed_from_mnemonic(phrase) restores the seed from the twenty five words as a bytearray. It ignores case and extra spacing and folds full width and other compatibility letters through a fixed table before it looks a word up, so a phrase pasted with capitals, non breaking spaces, or full width letters restores the same seed. A standard BIP-39 phrase from another wallet is refused with its own error, since a Quantova phrase carries a SHA3 checksum and cannot be restored from it. It takes the phrase as a str, bytes or bytearray. qcore.mnemonic_from_seed returns a bytearray, so overwrite it with zeros once the words have been shown, and qcore.mnemonic_from_seed_text returns a str for a caller that must display the words. A Python str can never be wiped, so prefer the bytes.
The Client checks every number the gateway reports before it signs. A fee or balance must be a string of the ASCII digits zero to nine, a nonce or head height must be a JSON integer below 2^53, and the head is either verified against a trusted validator roster or bounded by the genesis time and block rate of the network. The testnet carries both in the Rust core; on a private chain pass genesis_secs and blocks_per_sec to the Network, and a client with neither a roster nor a chain clock refuses to sign. A public network always needs a verified head. Every Client in a process shares one signing session per chain and account, so two clients never sign two transactions at one nonce, and a nonce the gateway reports below the one already reached is refused unless you name it as expected_nonce. A nonce it signed once is not signed again until you pass that nonce as expected_nonce, and a gateway that rejects a transaction and then reports a later nonce is refused for the same reason. Pass address_book, a list of Q1 addresses you trust, and the Client refuses to sign for a target that shares two or more leading and trailing characters with a known address, eight in all, but is a different address. The Client also reads contract state with storage, events, contract_nonce, contract_scalar, contract_map and asset_balance, and reaches https gateways with certificate verification and TLS 1.2 or later.
Client.call, Client.payable_call and Client.asset_call sign a contract call, a call that moves Quon and a call that moves an issued asset, each bounded by the fee ceiling you pass.
Every function that takes a seed accepts only a bytearray of thirty two bytes. It refuses a hex string and an immutable bytes object, since neither can be wiped. Keep the seed in a bytearray and wipe it with seed[:] = bytes(32) when you are done, and never print or log any part of it. The native extension wipes its own copy of a seed before it frees it.
A Client opened for a plain url with no network signs only for a private chain. It refuses the public testnet and any Q-main-net- chain until you name the network, with Network.testnet() or a mainnet network and acknowledge_mainnet=True. An expected nonce you pass must be the nonce the gateway reports, because the chain admits only the next nonce of an account. A rejected submission, or one whose validity deadline has passed, frees its nonce for the next send.
import quantova_qcore as qcore
client = qcore.Client(qcore.Network.testnet())
seed = qcore.generate_seed()
me = client.address(seed, 0)
to = client.address(seed, 1)
# Reads the fee and the nonce, signs inside the core, and submits. Nothing is
# signed or built in Python. The last argument is the highest fee you 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.
#
# Choose that ceiling yourself. Passing the gateway's own reported fee as the
# maximum nullifies the cap, because a value can never be above itself, so a
# malicious gateway could report any fee and it would always be accepted. Pick a
# fixed number you are willing to pay (the testnet fee is 500 Quon).
MAX_FEE = 1000 # Quon; the cap you enforce, not whatever the gateway reports
info = client.node_info()
# A fresh account holds nothing, so fund it first. On the public testnet a POST to
# https://q-test-net-01.quantova.org/faucet/api/claim with {"address": me, "amount": 10}
# sends 10 TQTOV, and the payment is itself a transaction so give it a few seconds.
#
# A fresh account funded by a transfer arrives with a balance but no key on the
# chain, so it signs this once to install its public key before its first send.
client.register(seed, 0, MAX_FEE)
signed, outcome = client.transfer(seed, 0, to, 1000, MAX_FEE)
status = client.transaction(signed["tx_id"])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, and every signature covers the genesis hash, so a transaction signed for one network never verifies on another network that reuses its name. A SigningSession reserves each nonce it hands out until it is signed, and a flow that stops early frees its nonce with release.
verify_finality measures signed stake against the active set of the roster you pass and verifies only when the signers hold strictly more than two thirds of it; a roster that gives two validators one attestation key is refused. The Client chooses the roster for a block from its verified height and the network epoch_length, never from the epoch the gateway names, and verify_finality_in_epoch applies the same check. 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.
A new session does not take an order nonce above zero from the gateway alone. Name the expected order nonce, or keep client.save_session() and pass it to client.restore_session(state) after a restart so tracked nonces and held signatures survive. The client refuses to sign for a retired chain name such as Q-test-net-1.
client.confirm_inclusion(tx_id, genesis_hash, roster, epoch) proves a transaction is inside a finalized block without trusting the gateway, checking the finality certificate against the genesis hash and the roster of that epoch you trust, then the transaction and its position. 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(tx_id, contract, selector_hex), which proves the event the contract emits in that block. client.transaction(tx_id) marks a status checked against a trusted roster with verified True, marks an unchecked one unverified, and refuses an unchecked status on mainnet. client.call_typed_order signs a contract order with the 24 hour expiry limit and the order nonce guard, and build_typed_order_call enforces the same expiry limit and refuses a second order with other terms at one order nonce unless resign=True.
A client signs for a public network only with a pinned genesis hash, taken from network.genesis_hash or the genesis_hash argument of Client, and refuses when the gateway reports a different genesis or none is pinned.
client.trust_roster(genesis_hash, 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 mainnet client refuses to sign until it has a trusted roster.
A call to a contract can carry a value in Quon alongside its arguments, and every payable call must name the chain it signs for so the signature can never be replayed onto another network. Read the chain id from the node you mean to reach rather than assuming one. The nonce comes from the account. The validity deadline is the height after which the chain drops the transaction, and a deadline of zero, which would never expire, is refused. A call pays one transfer fee for every started 1210 units of its meter limit, so the raw call signers take the transfer fee the node reports and refuse a fee below vm_call_fee(transfer_fee, meter_limit), a meter limit outside 1210 to 12500000, or call arguments above 128 KiB.
import quantova_qcore as qcore
client = qcore.Client(qcore.Network.testnet())
seed = qcore.generate_seed()
target = qcore.address(seed, 1)
info = client.node_info()
transfer_fee = int(info["fee"]["transfer_quon"])
signed = qcore.sign_payable_call(seed, 0, target, "", nonce=3, meter_limit=21000,
fee=qcore.vm_call_fee(transfer_fee, 21000), value=2500000,
chain_id=qcore.testnet_chain_id(),
valid_until=info["head_height"] + 300, transfer_fee=transfer_fee)maturin develop
The build needs maturin, which you install into a virtual environment with pip. Use maturin build for a release wheel.
Nothing here is published to a registry without an explicit release. This package handles a user's keys and a published version cannot be taken back.
QCore.py is built and owned by Quantova Inc and is generated over the Rust core QCore.rs, so the signing lives in one place and is never rewritten in Python. It carries none of the industry stack and inherits nothing from it. 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.