Creating a TallyBox Wallet
This tutorial guides tech-savvy users through the process of creating a secure TallyBox wallet using the secp256r1 elliptic curve. You'll generate a key pair, derive a unique wallet address, verify its integrity, and provision it for use in the TallyBox ecosystem, ensuring compatibility with TallyBox's cryptographic standards. The focus is on the algorithms and math behind each step, making it easy to implement in any programming language. Pseudo-code is provided for complex steps to aid implementation without libraries.
Step 1: Generate a Private-Public Key Pair
Use the secp256r1 elliptic curve (also known as P-256) to generate a key pair. This curve is defined by the equation y² = x³ + ax + b over a finite field, where a and b are specific constants, and the field size is a 256-bit prime (P). The process involves:
- Define the curve parameters: P (prime), a, b, G (base point), and n (order).
- Select a random 256-bit integer as the private key (d), within the range [1, n-1].
- Compute the public key (Q) as Q = d * G, where * denotes elliptic curve point multiplication.
- The public key Q is a point (X, Y) on the curve, where X and Y are 256-bit integers.
Secp256r1 Curve Parameters:
P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF // Prime field a = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC // Curve coefficient b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B // Curve coefficient Gx = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296 // Base point X Gy = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5 // Base point Y n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 // Order of the curve
Example Process:
Private Key: 7a8d26a42f45ecc68bc9d9ad2de9e4868429de2b185ab7e023f93845c58c4fa1
Public Key (Uncompressed):
X: 252df02599b4560df18cb5e7486769303c0ec0dcb0f64a5a980e41feba7d593f
Y: 7020405e0700461be964eaf159e3e20bd9aee2fda2f93b0c6ef03ee63876db47
References:
SEC 2: Recommended Elliptic Curve Domain Parameters (secp256r1)
Elliptic Curve Cryptography (Wikipedia)
Step 2: Compress and Encode the Public Key
Compress the public key to reduce its size, then encode it in Base58. The process involves:
- Take the X-coordinate of the public key (a 256-bit integer).
- Determine the parity of the Y-coordinate: if Y is odd, suffix = '1'; if Y is even, suffix = '2'. Parity is computed as Y mod 2.
- Form the compressed key as X + '*' + suffix (as a string).
- Encode the X-coordinate (as a byte array) in Base58, then append the suffix. Base58 encoding converts a byte array to a string using a 58-character alphabet, avoiding ambiguous characters like '0', 'O', 'I', and 'l'.
Pseudo-Code: Compress Public Key
FUNCTION compress_public_key(X, Y): parity = Y % 2 IF parity == 1 THEN suffix = '1' ELSE suffix = '2' END IF compressed_key = X + '*' + suffix RETURN compressed_key END FUNCTION
Pseudo-Code: Base58 Encode
FUNCTION base58_encode(bytes): alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' value = bytes_to_integer(bytes) // Convert byte array to big integer (unsigned) result = '' WHILE value > 0: remainder = value % 58 result = alphabet[remainder] + result value = value / 58 // Integer division END WHILE // Handle leading zeros in the byte array FOR each byte in bytes: IF byte == 0 THEN result = '1' + result ELSE BREAK END IF END FOR RETURN result END FUNCTION
Example Process:
Compressed Key: 252df02599b4560df18cb5e7486769303c0ec0dcb0f64a5a980e41feba7d593f*1
Base58 Compressed Public Key: 3W8iLTuAjbf9d1ih4RCi7Aat6keKxs72SNynZ1A269MY*1
References:
Base58 Encoding (Wikipedia)
Compressed Public Key Explanation (Bitcoin Stack Exchange)
Step 3: Derive the TallyBox Wallet Address
Derive a unique wallet address from the Base58-encoded public key. The process involves:
- Compute the SHA-256 hash of the Base58-encoded public key (treat the string as bytes using UTF-8 encoding).
- Ensure the SHA-256 hash is converted to a big integer as an unsigned value across all platforms (e.g., use unsigned conversion in C#, Python, Java, JavaScript).
- Encode the raw wallet string (as bytes) in Base58 (see pseudo-code in Step 2).
- Compute the MD5 hash of the Base58-encoded string (as bytes).
- Take the first 11 characters of the MD5 hash and prefix it with 'boxB' to form the checksum.
- Combine the checksum and the Base58-encoded string to form the final wallet address: checksum + Base58-encoded string.
Example Process:
Base58 Compressed Public Key: 3W8iLTuAjbf9d1ih4RCi7Aat6keKxs72SNynZ1A269MY*1
SHA-256 Hash: 5e3df94169f3d82b9b67d93acbc288a42c9812ced307297f78ed3058d338b1e4
Raw Wallet (Bigint): 42626905736173508565770948203518737897276237144762802425237770121056689369572
Base58 Encoded: 7Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP
MD5 Hash: a01d317e6c52656bb188a55736dc5aab
Checksum: boxBa01d317e6c5
Final Wallet Address: boxBa01d317e6c57Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP
References:
SHA-256 Algorithm (Wikipedia)
MD5 Algorithm (Wikipedia)
Base58 Encoding (Wikipedia)
Step 4: Verify the Key Pair Integrity
Verify the key pair to ensure it is valid for TallyBox transactions. The process involves:
- Sign the message "TallyBox, a tool for curious minds.." using the private key with the ECDSA (Elliptic Curve Digital Signature Algorithm) on the secp256r1 curve, following RFC 6979 for deterministic signature generation. This produces a signature (r, s).
- Decompress the compressed public key: given X and the suffix ('1' or '2'), compute Y using the curve equation y² = x³ + ax + b mod p, where p is the field prime. Solve for Y (two possible values) and select the one matching the parity (odd for '1', even for '2').
- Verify the signature using the decompressed public key (X, Y) with ECDSA. If the signature is valid, the key pair is trustworthy.
- If verification fails, discard the key pair and restart from Step 1.
Example Process:
Message Signed: "TallyBox, a tool for curious minds.."
Signature (Base64): MEYCIQD6BLlP29AzfKK/SPyuMdVYrWzn/wQElsdkVF+ewvBgDQIhANy4lgejq9JChhQM/9PWyxfLKJYxRz4SWcoBak468i5s
Base58 Compressed Public Key: 3W8iLTuAjbf9d1ih4RCi7Aat6keKxs72SNynZ1A269MY*1
Decompressed Public Key (X*Y): 252df02599b4560df18cb5e7486769303c0ec0dcb0f64a5a980e41feba7d593f*7020405e0700461be964eaf159e3e20bd9aee2fda2f93b0c6ef03ee63876db47
Signature Verification Result: Valid
References:
ECDSA Algorithm (Wikipedia)
SEC 2: Recommended Elliptic Curve Domain Parameters (secp256r1)
RFC 6979: Deterministic Usage of DSA and ECDSA
Step 5: Provision the TallyBox Wallet
Provision the wallet by securely storing its data. The process involves:
- Select a wallet name and a strong local password.
- Form a key string as wallet_name + '~' + password + '~' + wallet_address.
- Compute the SHA-256 hash of the key string (as bytes) to generate a 64-character hex secret key.
- Encrypt the private key (as a hex string) using a special AES-256-CBC algorithm with custom padding, described below. Encode the ciphertext in Base64.
- Decrypt the encrypted private key to verify correctness: use the same secret key and decrypt the ciphertext. Compare the result with the original private key.
- Create an XML file containing the wallet name, wallet address, Base58-encoded compressed public key, encrypted private key (Base64), and a tech info URL.
- Store the XML file and private key offline securely.
Special AES-256-CBC with Custom Padding
TallyBox uses a unique AES-256-CBC encryption algorithm with custom padding to secure 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). Random padding is added around the plaintext for enhanced security. 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: 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.
- Output: Base64-encoded ciphertext (no IV prepended).
Pseudo-Code: AES-256-CBC Encrypt with Custom Padding
FUNCTION aes256_cbc_encrypt_custom(data, secret): IF length(secret) < 64 OR NOT is_hex(secret) THEN THROW "Secret must be a 64-char hex string" END IF 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) left_padding_size = random_integer(0, 99) left_padding = random_string(left_padding_size, 'a-zA-Z') right_padding_size = random_integer(0, 99) right_padding = random_string(right_padding_size, 'a-zA-Z') padded_data = left_padding + '|' + data + '|' + right_padding padding_length = 16 - (length(padded_data) % 16) padded_data = padded_data + (padding_length as byte) * padding_length // PKCS#7 cipher = initialize_aes256_cbc_encrypt(key, iv) ciphertext = cipher.encrypt(padded_data) base64_result = base64_encode(ciphertext) RETURN base64_result END FUNCTION
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
Example Process:
Original Private Key (hex): 7a8d26a42f45ecc68bc9d9ad2de9e4868429de2b185ab7e023f93845c58c4fa1
Key Components (pre-SHA-256): shahin~11~boxBa01d317e6c57Lt8jmf3GNWCJc2K5bU7aAS5gqSbDYnY5UVshnJVHEJP
Local Key (SHA-256): 7e9b4699f7d3eea878f51a9d4e238922116cccc558b15f2db257d92a9779becc
Encrypted Private Key (Base64): Lu8xEpnACOtVrmZxRt7I2m7Zc5pZI8sxIGhXV6FqwN6N2VG8yt4jof5c0Xo5slF+VyOLORhls2auG27Xmjd20Z/9YiBZ2E5ePFsQV7ZkeqKFZa+k5IrOHGjDcyJtiJt22zAzOaSvyTFFGRHwDGI1FCJ5BppBvYkoGhro9MxHp3MVS7eW2N3z2NEQV2Gxm+IErv0ExkxfesyXqw/kTwk2qHBuTUtKRgkOY8RCAPPkNglOjfo4ct+YCdkzoIS/Ww6xatWmAUA6bhtxK2q3vZly1iteNAa+Uz0Pyn166f7Y90cCGJPINc5x7WGcolqPeWf5
Decrypted Private Key (hex): 7a8d26a42f45ecc68bc9d9ad2de9e4868429de2b185ab7e023f93845c58c4fa1
Verification Result: Verified
Example Output:
References:
AES Encryption (Wikipedia)
SHA-256 Algorithm (Wikipedia)
XML Specification (W3C)
Acknowledgments
Special thanks to Grok, for its invaluable assistance in creating this TallyBox wallet creation tutorial.