Disclosure: This tutorial was written and tested with an AI coding agent for a submission to the RustChain content bounty. No bounty has been accepted or paid. The example below uses synthetic public bytes, not a wallet receiving funds.
If you contribute to an open-source project that pays in its own token, one small question matters before you paste a receiving address: what kind of identifier does the project expect? A GitHub handle, a native address, and an Ethereum-style address are not interchangeable merely because all three fit in a comment box.
RustChain's content-round discussion illustrates this problem. Maintainer replies distinguish a named hosted balance from an address backed by a keypair, and explain that a hosted balance can require a maintainer-assisted migration before its recipient can sign transfers. That makes a useful engineering exercise: build a small check that tells us exactly what it has established, without pretending it verified ownership or payment.
This is an offline tutorial. It does not create keys, open wallet files, query a node, or submit a transaction. You can run it with Python's standard library. Its deliberately limited scope makes the result easier to interpret and reproduce.
Follow a concrete implementation
In the RustChain wallet CLI, _address_from_pubkey_hex computes the native address as RTC followed by the first 40 hexadecimal characters of SHA-256 over the decoded public-key bytes. The fetched file had Git blob SHA 3940526abf54aca6276f867fc19cf6faab4a90a3 when this tutorial was prepared.
That detail about decoded bytes matters. Hashing the 64-character text representation produces a different digest from hashing its 32 decoded bytes. A checker that forgets bytes.fromhex can look plausible while rejecting correct pairs.
The CLI generates Ed25519 public keys. Here we check the expected byte length and derivation; we do not validate whether arbitrary bytes represent a usable Ed25519 point. We also choose a strict canonical spelling: uppercase RTC, followed by lowercase hex. This is a local input policy matching the producer's output, not a claim that every node rejects every alternative spelling.
Four separate questions
An address can have the expected shape while belonging to nobody who can sign for it. Adding a public key lets us ask whether that key hashes to the address, but anybody can copy somebody else's public key. A successful pair check therefore still proves no control over the private key.
Ownership verification would need an appropriate signature challenge and verification process. Payment verification would need separate ledger evidence. Neither belongs in an offline string checker. The output keeps ownership_proven and payment_verified false even when pair_matches is true, so a caller cannot mistake this result for either kind of proof.
Save the following complete program as address_check.py:
"""Offline format/pair checker; never proves wallet ownership or payment."""
import argparse
import hashlib
import json
import re
import sys
def validate_address(address):
if not isinstance(address, str) or not re.fullmatch(r'RTC[0-9a-f]{40}', address):
raise ValueError('Expected canonical RTC followed by 40 lowercase hex characters')
return address
def check_pair(address, public_key=None):
validate_address(address)
result = {'address_format': 'canonical', 'pair_matches': None,
'ownership_proven': False, 'payment_verified': False}
if public_key is not None:
if not re.fullmatch(r'[0-9a-fA-F]{64}', public_key):
raise ValueError('Expected a 32-byte public key encoded as 64 hex characters')
derived = 'RTC' + hashlib.sha256(bytes.fromhex(public_key)).hexdigest()[:40]
result['pair_matches'] = derived == address
return result
def main():
parser = argparse.ArgumentParser(description=__doc__,
epilog='Exit codes: 0 = checks supplied passed; 1 = pair mismatch; 2 = invalid input. '
'Use public information only. No network or wallet files are accessed.')
parser.add_argument('address')
parser.add_argument('--public-key', help='Public key only; never a private key or seed phrase')
args = parser.parse_args()
try:
result = check_pair(args.address, args.public_key)
except ValueError as exc:
print(str(exc), file=sys.stderr)
return 2
print(json.dumps(result, sort_keys=True))
return 1 if result['pair_matches'] is False else 0
if __name__ == '__main__':
sys.exit(main())
Run a reproducible example
Use the public byte sequence 00 through 1f as a synthetic input. It is not a generated receiving wallet. Do not send anything to the resulting example address.
python address_check.py RTC630dcd2966c4336691125448bbb25b4ff412a49c --public-key 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Captured output from the local Windows run:
{"address_format": "canonical", "ownership_proven": false, "pair_matches": true, "payment_verified": false}
The process exits with code 0. Replace the public key with 32 01 bytes and it exits with code 1, reporting pair_matches: false. Supply an Ethereum-shaped identifier beginning with 0x and it exits with code 2 and an input error on stderr. Omit --public-key, and the result contains pair_matches: null: that check was not performed.
Exit code 0 means only that the supplied checks passed. For an address-only call, it means canonical syntax passed. Your integration must inspect the fields appropriate to its purpose, instead of treating a successful process as a general approval to pay.
Test the boundary, not just the happy path
The supporting lab runs six unit tests covering a matching pair, a mismatched key, address-only semantics, other identifier formats, noncanonical casing, and malformed public-key input. It also invokes the actual command-line program in four subprocess cases and checks their exit codes and output flags.
For an additional cross-check, the lab extracts only the upstream address function from the downloaded source and compares the synthetic example with that function. It does not import the entire wallet CLI: that module creates a home-directory wallet folder during import. This distinction keeps a documentation check from unexpectedly initializing wallet storage.
The lab passed all six tests and all four CLI cases. It does not establish a live balance, confirm a transfer, or prove control of any wallet. Those remain separate steps. A useful preflight tool should make its boundary visible in its output, especially when another program will act on that output.
The content-round discussion contains the receiving-address context for this exercise. Consult the project's current wallet documentation for actual wallet creation; this lab is an identifier check, not a replacement wallet implementation.
Top comments (0)