Preparing a Transaction with Tallybox Wallet
This tutorial guides you through loading a Tallybox Wallet, checking token balances, and preparing and broadcasting a transaction on a Directed Acyclic Graph (DAG) network. Follow the steps below to implement your own transaction preparation code.
References:
Directed Acyclic Graph (Wikipedia)
Cryptocurrency Wallet (Wikipedia)
Step 1: Load Wallet and Decrypt Private Key
Begin by loading the wallet's XML file, which contains the wallet name, compressed public key, encrypted private key, and wallet address. Prompt the user to select an XML file and enter a local password for decryption. Validate the XML structure and decrypt the private key using the password using a special AES-256-CBC algorithm with custom padding, described below.
Special AES-256-CBC with Custom Padding
Tallybox uses a unique AES-256-CBC decryption algorithm with custom padding to retrieve the private key. The algorithm derives the key, IV, and salt from a 64-character hex secret key (SHA-256 of wallet_name~password~wallet_address). The plaintext is expected to have random padding around it. The configuration is:
- Secret Key: A 64-character hex string (32 bytes), split into:
- Password: First 32 hex chars (16 bytes, ASCII-encoded).
- IV: Next 16 hex chars (8 bytes, ASCII-encoded, 16 bytes as bytes).
- Salt: Last 16 hex chars (8 bytes, ASCII-encoded).
- Key Derivation: PBKDF2 with SHA-256, 3 iterations, 32-byte output, using password and salt.
- Custom Padding: Expected format is random string (0–99 chars, a-zA-Z) + '|' + plaintext (64-char hex private key) + '|' + random string (0–99 chars, a-zA-Z), followed by PKCS#7 padding.
- Input: Base64-encoded ciphertext (no IV prepended).
Pseudo-Code: AES-256-CBC Decrypt with Custom Padding
FUNCTION aes256_cbc_decrypt_custom(base64_ciphertext, secret): IF length(secret) < 64 OR NOT is_hex(secret) THEN THROW "Secret must be a 64-char hex string" END IF ciphertext = base64_decode(base64_ciphertext.replace('\n', '')) // Strip newlines password = secret[0:32] // First 32 hex chars (ASCII) iv = secret[32:48] // Next 16 hex chars (ASCII) salt = secret[48:64] // Last 16 hex chars (ASCII) key = PBKDF2_HMAC_SHA256(password, salt, iterations=3, output_bytes=32) cipher = initialize_aes256_cbc_decrypt(key, iv) padded_data = cipher.decrypt(ciphertext) padding_length = padded_data[-1] IF padding_length > 16 OR padding_length == 0 THEN THROW "Invalid PKCS#7 padding" END IF padded_data = padded_data[:-padding_length] // Remove PKCS#7 padding padded_text = padded_data.decode('utf-8') parts = padded_text.split('|') IF length(parts) != 3 THEN THROW "Invalid padding format: expected 'left|data|right'" END IF plaintext = parts[1] IF NOT is_hex(plaintext) OR length(plaintext) != 64 THEN THROW "Decrypted private key must be a 64-char hex string" END IF RETURN plaintext END FUNCTION
Sample Pseudo-Code: Load Wallet
FUNCTION load_wallet(file, password, protocol, graph): TRY: xml_content = READ_FILE(file) xml_doc = PARSE_XML(xml_content) wallet_name = xml_doc.GET_TAG("wallet_name") public_key_b58 = xml_doc.GET_TAG("public_key_b58_compressed") private_key_aes_b64 = xml_doc.GET_TAG("private_key_aes_b64") wallet_address = xml_doc.GET_TAG("wallet_address") IF NOT (wallet_name AND public_key_b58 AND private_key_aes_b64 AND wallet_address): RETURN ERROR("Invalid XML format") key_components = wallet_name + "~" + password + "~" + wallet_address local_key = SHA256(key_components) private_key = aes256_cbc_decrypt_custom(private_key_aes_b64, local_key) ec = NEW_ELLIPTIC_CURVE("p256") key_pair = ec.KEY_FROM_PRIVATE(private_key, "hex") public_key = key_pair.GET_PUBLIC() x_hex = public_key.GET_X().TO_HEX().PAD_START(64, "0") y_hex = public_key.GET_Y().TO_HEX().PAD_START(64, "0") y_parity = public_key.GET_Y().MOD(2) suffix = IF y_parity == 0 THEN "2" ELSE "1" compressed_key = x_hex + "*" + suffix result = CREATE_RECORD() result.SET_FIELD("key_pair", key_pair) result.SET_FIELD("compressed_key", compressed_key) result.SET_FIELD("public_key_b58", public_key_b58) result.SET_FIELD("wallet_address", wallet_address) result.SET_FIELD("wallet_name", wallet_name) RETURN result CATCH error: RETURN ERROR("Decryption failed or invalid file: " + error) END FUNCTION
Notice: XML File Structure
The XML file must include
,
,
, and
. Ensure the password matches
the one used to encrypt the private key, or decryption will fail.
Example Process:
Step 1 - Loaded Wallet Name:
shahin
Step 2 - Extracted Public Key (Base58 Compressed):
3W8iLTuAjbf9d1ih4RCi7Aat6keKxs72SNynZ1A269MY*1
Step 3 - Extracted Wallet Address:
boxBa01d317e6c57Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP
Step 4 - Key Components (pre-SHA-256):
shahin~11~boxBa01d317e6c57Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP
Step 5 - Local Key (SHA-256):
7e9b4699f7d3eea878f51a9d4e238922116cccc558b15f2db257d92a9779becc
Step 6 - Encrypted Private Key (Base64):
Lu8xEpnACOtVrmZxRt7I2m7Zc5pZI8sxIGhXV6FqwN6N2VG8yt4jof5c0Xo5slF+VyOLORhls2auG27Xmjd20Z/9YiBZ2E5ePFsQV7ZkeqKFZa+k5IrOHGjDcyJtiJt22zAzOaSvyTFFGRHwDGI1FCJ5BppBvYkoGhro9MxHp3MVS7eW2N3z2NEQV2Gxm+IErv0ExkxfesyXqw/kTwk2qHBuTUtKRgkOY8RCAPPkNglOjfo4ct+YCdkzoIS/Ww6xatWmAUA6bhtxK2q3vZly1iteNAa+Uz0Pyn166f7Y90cCGJPINc5x7WGcolqPeWf5
Step 7 - Decrypted Private Key (hex):
7a8d26a42f45ecc68bc9d9ad2de9e4868429de2b185ab7e023f93845c58c4fa1
Step 8 - Status:
Wallet loaded successfully!
References:
XML Specification (W3C)
AES Encryption (Wikipedia)
SHA-256 Algorithm (Wikipedia)
SEC 2: Recommended Elliptic Curve Domain Parameters (secp256r1)
Step 2: Choose Action (History or Send)
After successfully loading the wallet, prompt the user to either view their token balances (history) or prepare and sign a transaction to send tokens. Ensure the wallet is loaded before proceeding to either action.
Sample Pseudo-Code: User Choice
FUNCTION prompt_user_action(wallet_state): IF NOT wallet_state.wallet_address: RETURN ERROR("No wallet loaded. Load a wallet first.") action = PROMPT_USER("Select action: [1] View History, [2] Send Transaction") IF action == "1": CALL fetch_balances(wallet_state) ELSE IF action == "2": CALL prepare_transaction(wallet_state) ELSE RETURN ERROR("Invalid action selected") END FUNCTION
Warning: Wallet Validation
Always verify that a wallet is loaded (i.e., wallet_address
is not empty) before allowing history
or transaction actions. Attempting to proceed without a loaded wallet will result in errors.
Example Process:
No example output provided for this step.
References:
User Interface Design (Wikipedia)
Control Flow in Programming (Wikipedia)
Step 3: Fetch Token Balances
To view balances, send a POST request to the DAG node's API endpoint
(/archive.asmx/order_history
) with the wallet address and graph details. Parse the response to
extract balances for supported tokens (e.g., 2PN, 2ZR, TLH).
Sample Pseudo-Code: Fetch Balances
FUNCTION fetch_balances(wallet_state): url = wallet_state.protocol + "://" + wallet_state.graph + "/archive.asmx/order_history" post_data = "app_name=tallybox&app_version=2.0&in_graph=" + ENCODE_URI_COMPONENT(wallet_state.graph) + "&wallet_address=" + ENCODE_URI_COMPONENT(wallet_state.wallet_address) TRY: response = POST_REQUEST(url, post_data) parts = response.SPLIT("~") balances = CREATE_RECORD() balances.SET_FIELD("2PN", "0.00000000") balances.SET_FIELD("2ZR", "0.00000000") balances.SET_FIELD("TLH", "0.00000000") FOR i = 6 TO parts.LENGTH - 2 STEP 2 token = parts[i] amount = parts[i + 1] IF token IN ["2PN", "2ZR", "TLH"] AND amount AND IS_NUMBER(amount): balances.SET_FIELD(token, FORMAT_DECIMAL(amount, 8)) END IF END FOR RETURN balances CATCH error: RETURN ERROR("Failed to fetch balances: " + error) END FUNCTION
Info: API Endpoint
The default graph is tallybox.mixoftix.net
, and the protocol is typically https
.
The API returns a tilde-separated string where token balances appear as pairs (e.g., 2PN~123.456789
).
Example Process:
Step 0 - Current Date-Time:
2025-04-17 04:30:08
Step 1 - Constructed POST Data:
app_name=tallybox&app_version=2.0&in_graph=tallybox.mixoftix.net&wallet_address=boxBa01d317e6c57Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP
Step 2 - Server Endpoint URL:
https://tallybox.mixoftix.net/archive.asmx/order_history
Step 3 - Server Response:
tallybox.mixoftix.net~boxBa01d317e6c57Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP~msg~null~adv~null~null
Step 4 - Status:
History fetched successfully!
References:
HTTP POST Request (Wikipedia)
URI Encoding (Wikipedia)
API Concepts (Wikipedia)
Step 4: Prepare Transaction
Prompt the user for the target wallet address, token name (e.g., 2PN, 2ZR, TLH), amount, and an optional order ID. Validate the target wallet address to ensure it conforms to Tallybox's format. Generate an offline signature using the private key with the ECDSA (Elliptic Curve Digital Signature Algorithm) on the secp256r1 curve, following RFC 6979 for deterministic signature generation, to create a signed transaction ready for broadcasting.
Sample Pseudo-Code: Prepare Transaction
FUNCTION prepare_transaction(wallet_state): target_address = PROMPT_USER("Enter target wallet address") token = PROMPT_USER("Select token: [2PN, 2ZR, TLH]") amount = PROMPT_USER("Enter amount (positive number)") order_id = PROMPT_USER("Enter optional order ID", optional=true) graph = wallet_state.graph utc_unix = CURRENT_TIMESTAMP_SECONDS() IF NOT VALIDATE_WALLET_ADDRESS(target_address): RETURN ERROR("Invalid target wallet address") IF NOT (token IN ["2PN", "2ZR", "TLH"]): RETURN ERROR("Invalid token") IF NOT IS_NUMBER(amount) OR amount <= 0: RETURN ERROR("Invalid amount") transaction_data = ENCODE_URI_COMPONENT(graph) + "~" + ENCODE_URI_COMPONENT(graph) + "~" + ENCODE_URI_COMPONENT(wallet_state.wallet_address) + "~" + ENCODE_URI_COMPONENT(target_address) + "~" + ENCODE_URI_COMPONENT(token) + "~" + FORMAT_DECIMAL(amount, 8) + "~" + ENCODE_URI_COMPONENT(order_id) + "~" + utc_unix msg_hash = SHA256(transaction_data) ec = NEW_ELLIPTIC_CURVE("p256") signature = wallet_state.key_pair.SIGN_ECDSA(msg_hash, RFC6979) // Produces (r, s) pair, deterministic per RFC 6979 sig_der = signature.TO_DER("hex") // Encode (r, s) as DER, hex string sig_base64 = BASE64_ENCODE(HEX_TO_BYTES(sig_der)) // Convert hex to bytes, encode as Base64 broadcast_data = CONCATENATE_WITH_SEPARATOR( "tallybox", "parcel_of_transaction", "graph_from", ENCODE_URI_COMPONENT(graph), "graph_to", ENCODE_URI_COMPONENT(graph), "wallet_from", ENCODE_URI_COMPONENT(wallet_state.wallet_address), "wallet_to", ENCODE_URI_COMPONENT(target_address), "order_currency", ENCODE_URI_COMPONENT(token), "order_amount", FORMAT_DECIMAL(amount, 8), "order_id", ENCODE_URI_COMPONENT(order_id), "order_utc_unix", utc_unix, "the_sign", ENCODE_URI_COMPONENT(sig_base64), "publicKey_xy_compressed", ENCODE_URI_COMPONENT(wallet_state.public_key_b58), separator="~" ) RETURN broadcast_data END FUNCTION
Sample Pseudo-Code: Validate Wallet Address
FUNCTION VALIDATE_WALLET_ADDRESS(address): // Check for null/empty or insufficient length IF address IS NULL OR address.LENGTH < 40: RETURN FALSE END IF // Check prefix IF NOT address.STARTS_WITH("box"): RETURN FALSE END IF // Check curve character (4th character) curve_char = address[3] IF curve_char NOT IN ["A", "B", "C"]: RETURN FALSE END IF // Checksum validation checksum_md5 = address.SUBSTRING(4, 15) // Characters 4 to 14 (11 characters) base58_part = address.SUBSTRING(15) // From character 15 to end computed_md5 = MD5(base58_part.encode()).SUBSTRING(0, 11) IF checksum_md5 == computed_md5: RETURN TRUE END IF RETURN FALSE END FUNCTION
Success: Transaction Signing
A successful offline signature produces a tilde-separated string containing the transaction details,
signature, and public key, ready for broadcasting to the DAG node.
Note: The ECDSA signing process must comply with RFC 6979 to ensure deterministic signatures,
enhancing security and reproducibility.
Example Process:
Step 1 - Transaction Data:
tallybox.mixoftix.net~tallybox.mixoftix.net~boxBa01d317e6c57Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP~boxB8cbfbaf9c1c4Pfyp4JMNThyUhNh4PPdD7hiXBeTJgQtU7npfZSMEH68~2ZR~34.54000000~~1744889500
Step 2 - SHA-256 Hash:
d838bdb3dfe7f3a8b07931c99f90080d46c0757fbe994a9863a6135ef5645006
Step 3 - Signature (Base64):
MEUCIQDDK5ALX00hImyHHL/dpKGDMnYLmLG6hkz9Q4vmUsydFQIgLd88G7AUp2IbQMwyNAIAlrSqYlh8zCzq7jOXj+JzmDM=
Step 4 - Broadcast Result:
tallybox~parcel_of_transaction~graph_from~tallybox.mixoftix.net~graph_to~tallybox.mixoftix.net~wallet_from~boxBa01d317e6c57Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP~wallet_to~boxB8cbfbaf9c1c4Pfyp4JMNThyUhNh4PPdD7hiXBeTJgQtU7npfZSMEH68~order_currency~2ZR~order_amount~34.54000000~order_id~~order_utc_unix~1744889500~the_sign~MEUCIQDDK5ALX00hImyHHL%2FdpKGDMnYLmLG6hkz9Q4vmUsydFQIgLd88G7AUp2IbQMwyNAIAlrSqYlh8zCzq7jOXj%2BJzmDM%3D~publicKey_xy_compressed~3W8iLTuAjbf9d1ih4RCi7Aat6keKxs72SNynZ1A269MY*1
Example Output:
References:
ECDSA Algorithm (Wikipedia)
RFC 6979: Deterministic Usage of DSA and ECDSA
SHA-256 Algorithm (Wikipedia)
Base64 Encoding (Wikipedia)
MD5 Algorithm (Wikipedia)
URI Encoding (Wikipedia)
Step 5: Broadcast Transaction
Broadcast the signed transaction to the DAG node's API endpoint
(/broadcast.asmx/order_accept
) using a POST request. Handle the response to confirm whether
the transaction was accepted or rejected by the network.
Sample Pseudo-Code: Broadcast Transaction
FUNCTION broadcast_transaction(wallet_state, broadcast_data): url = wallet_state.protocol + "://" + wallet_state.graph + "/broadcast.asmx/order_accept" post_data = "app_name=tallybox&app_version=2.0&order_csv=" + ENCODE_URI_COMPONENT(broadcast_data.REPLACE("\n", "").REPLACE("\r", "")) TRY: response = POST_REQUEST(url, post_data) IF response.STARTS_WITH("submitted~200~"): RETURN SUCCESS("Transaction broadcast successfully: " + response) ELSE RETURN ERROR("Broadcast failed: " + response) CATCH error: RETURN ERROR("Failed to broadcast transaction: " + error) END FUNCTION
Success: Transaction Broadcasting
A successful broadcast returns a response starting with submitted~200~
, indicating the transaction
was accepted by the DAG node. The response includes a transaction ID for tracking.
Example Process:
Step 1 - Constructed POST Data:
app_name=tallybox&app_version=2.0&order_csv=tallybox~parcel_of_transaction~graph_from~tallybox.mixoftix.net~graph_to~tallybox.mixoftix.net~wallet_from~boxBa01d317e6c57Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP~wallet_to~boxB8cbfbaf9c1c4Pfyp4JMNThyUhNh4PPdD7hiXBeTJgQtU7npfZSMEH68~order_currency~2ZR~order_amount~34.54000000~order_id~~order_utc_unix~1744889500~the_sign~MEUCIQDDK5ALX00hImyHHL%2FdpKGDMnYLmLG6hkz9Q4vmUsydFQIgLd88G7AUp2IbQMwyNAIAlrSqYlh8zCzq7jOXj%2BJzmDM%3D~publicKey_xy_compressed~3W8iLTuAjbf9d1ih4RCi7Aat6keKxs72SNynZ1A269MY*1
Step 2 - Server Endpoint URL:
https://tallybox.mixoftix.net/broadcast.asmx/order_accept
Step 3 - Server Response:
error~203~no enough fee~order_amount
Step 4 - Status:
Broadcast failed!
References:
HTTP POST Request (Wikipedia)
URI Encoding (Wikipedia)
API Concepts (Wikipedia)
Acknowledgments
Special thanks to Grok, for its invaluable assistance in creating this TallyBox wallet transaction tutorial.