remotemass (OP)
Legendary
Offline
Activity: 1122
Merit: 1017
ASMR El Salvador
|
 |
February 13, 2013, 11:40:49 AM Last edit: February 25, 2025, 12:35:13 PM by remotemass |
|
######################################### Up to Date: https://cubicpostcode.com/bitcoinMorse.html######################################### Bitcoin addresses allow: 0 .. 9, A ..Z, a .. z, but without the four characters 0, O, I and l I wanted to encode any bitcoin address using only numbers. Because zeros are not allowed we will use it to encode letters like this, preceding with zeros for a letter, like this: 01-A 001-a (double zero, one) 02-B 002-b 03-C 003-c 04-D 004-d 05-E 005-e 06-F 006-f 07-G 007-g 08-H 008-h 009-i 0001-J (triple zero, one) 00001-j (fourfold zero, one) 0002-K 00002-k 0003-L 0004-M 00004-m 0005-N 00005-n 00006-o 0007-P 00007-p 0008-Q 00008-q 0009-R 00009-r 000001-S (fivefold zero, one) 0000001-s (sixfold zero, one) 000002-T 0000002-t 000003-U 0000003-u 000004-V 0000004-v 000005-W 0000005-w 000006-X 0000006-x 000007-Y 0000007-y 000008-Z 0000008-z So, best scenario you would encode a bitcoin address with 34 digits and in the worst case scenario with 238 digits. With this encoding the lengh would be very variable. Let me try to encode one address with this 'Bitcoin Morse', as I call it. Bitcoin Address:14jsRJx8HnKq9jyqfBQKgvvnFy3rNCbc3G In Bitcoin Morse: 1400001000000100090001000000680800005000200008900001000000700008006020008000200 70000004000000400005060000007300009000503002003307 34 alphanumerics into 129 number digits. Not too bad, is it?! And maybe vanity addresses could optimize your address to have as little lenght as it gets...
|
" PetroPayPal ≠ PayPal. Sats > petrocash. Stack free. " — #Cubic #Postcode.
|
|
|
jerfelix
|
 |
February 13, 2013, 11:55:44 AM |
|
A more mathematically sound way to convert a Bitcoin address to a string of digits would be to take the Bitcoin Address, which is a Base-58 number, and simply convert it to decimal. This would be trivial programmatically.
It wouldn't have the properties that you described, in that it's length would always be much smaller than what you described, and usually be nearly the same length (for all randomly generated Bitcoin Addresses).
|
|
|
|
ciphermonk
Newbie
Offline
Activity: 50
Merit: 0
|
 |
February 13, 2013, 01:59:22 PM Last edit: February 14, 2013, 07:10:13 AM by ciphermonk |
|
Edit2: This example is flawed. See my post below for a better example. Here's an example of base-10 encoding. bitcoin address: msTuyWdqBoWFDhttLrqagzqi8n5KWaD3Pvbase 16 (hex) encoding: 830e533c2b8a12a1a06b56f08578645b72e324bbbase 10 encoding: 975382009484802281201214682924674052502252791847Here's the perl script I used to calculate the base10 encoding from raw bitcoin data: https://gist.github.com/ciphermonk/4944777I did not do any verification if it's correct, but at least the spirit is there  As far as classical information theory goes, this is the smallest base10 compression you can get. Cheers! Edit: This example above is wrong: The raw data I used ( from a raw transaction ) does not contain the bitcoin address version and does not contain the checksum. So the actual base 10 encoding would have a few more digits  Sorry about that. If anyone want to have an updated script, let me know I can work on it.
|
|
|
|
dserrano5
Legendary
Offline
Activity: 1974
Merit: 1030
|
 |
February 13, 2013, 02:48:29 PM |
|
Why @b10 and %b10? $b10[n] == $b10{n} == n so they are useless (they make sense for other radices but not for <= 10). Also, the pack/unpack is redundant, and the result must be reversed: use bigint;
sub encode_base10 { my $bigint = hex shift; my $result = ''; while ($bigint > 0) { my ($quo, $rem) = $bigint->bdiv(10); $result .= $rem; } scalar reverse $result; } print encode_base10 '830e533c2b8a12a1a06b56f08578645b72e324bb'; The while loop can be written in one line while maintaining full readability but I'll leave that aside  .
|
|
|
|
payb.tc
|
 |
February 13, 2013, 03:08:41 PM |
|
The while loop can be written in one line while maintaining full readability but I'll leave that aside  . please tell a complete perl novice (me) how you would write it 
|
|
|
|
ciphermonk
Newbie
Offline
Activity: 50
Merit: 0
|
 |
February 13, 2013, 03:34:45 PM |
|
Why @b10 and %b10? $b10[n] == $b10{n} == n so they are useless (they make sense for other radices but not for <= 10). Also, the pack/unpack is redundant, and the result must be reversed: use bigint;
sub encode_base10 { my $bigint = hex shift; my $result = ''; while ($bigint > 0) { my ($quo, $rem) = $bigint->bdiv(10); $result .= $rem; } scalar reverse $result; } print encode_base10 '830e533c2b8a12a1a06b56f08578645b72e324bb'; The while loop can be written in one line while maintaining full readability but I'll leave that aside  . You are right, it's quite irrelevant to write that. The fact of the matter is that $bigint already holds the base10 representation, so there's no point going through the while loop. I simply wanted to demonstrate how you encode arbitrary data into a basex representation. Thanks for improving the code.
|
|
|
|
dserrano5
Legendary
Offline
Activity: 1974
Merit: 1030
|
 |
February 13, 2013, 05:32:52 PM |
|
The fact of the matter is that $bigint already holds the base10 representation, so there's no point going through the while loop.
Doh, I completely overlooked that  . E&G: $result .= ($bigint->bdiv(10))[1] while $bigint;
|
|
|
|
ciphermonk
Newbie
Offline
Activity: 50
Merit: 0
|
 |
February 14, 2013, 07:08:59 AM Last edit: February 14, 2013, 07:20:23 AM by ciphermonk |
|
$result .= ($bigint->bdiv(10))[1] while $bigint;
Thats cool. I like it, except that the ()[1] is somewhat obfuscated. Someone reading the code needs to open the bigint documentation to understand the behavior. It's a tradeoff between succinctness and readability. Both views can be defended  For completeness, I decided to run the OP's example address to base10: 14jsRJx8HnKq9jyqfBQKgvvnFy3rNCbc3GIf you run this through decode_base58, you get the following string ( hex representation ): 002903ef7df972ef94adeda53f0ccd24699de872041e4732cfThe first byte ( 00 ) is a bitcoin address version string. On production blockchain, this is 0x00 and on testnet blockchain, this is 0x6f ( or 111 in decimal representation ). We notice that this is a production address. The last 4 bytes ( 1e4732cf ) is a checksum over the previous data. If you make a typo while manually entering a bitcoin address, you will create an invalid checksum and the bitcoin client will refuse to send money to it. So, we want to convert this hexadecimal representation into decimal representation. Turns out to be very easy in perl: use bigint; print hex '002903ef7df972ef94adeda53f0ccd24699de872041e4732cf';
The result is: 1005694022349920422888116886380815406116626226984035758799I believe the result is correct now  Cheers!
|
|
|
|
jerfelix
|
 |
February 14, 2013, 08:07:07 AM |
|
It seems like about the correct length.
34 Base-58 digits should convert to about 60 decimal digits, since 34*ln(58)/ln(10) = 59.9. Since the Bitcoin Address starts with leading zeros (a 1 in Base-58, where zero has been eliminated from the digit pool), your 59 digit number seems in the right range.
|
|
|
|
dserrano5
Legendary
Offline
Activity: 1974
Merit: 1030
|
 |
February 14, 2013, 08:37:17 AM |
|
Thats cool. I like it, except that the ()[1] is somewhat obfuscated. Someone reading the code needs to open the bigint documentation to understand the behavior. It's a tradeoff between succinctness and readability. Both views can be defended  It's not bigint-related, just standard syntax to access the second element in a list. Another example: $ date; perl -le 'print join " ", (localtime)[2,1,0]' Thu Feb 14 09:36:19 CET 2013 9 36 19 But we're way off topic now.
|
|
|
|
ciphermonk
Newbie
Offline
Activity: 50
Merit: 0
|
 |
February 14, 2013, 11:26:33 AM |
|
$result .= ($bigint->bdiv(10))[1] while $bigint;
Thats cool. I like it, except that the ()[1] is somewhat obfuscated. Someone reading the code needs to open the bigint documentation to understand the behavior. It's a tradeoff between succinctness and readability. Both views can be defended  I'm sorry, perhaps I didn't express myself correctly. What I meant is that someone reading the code might not know that the second element in the list is the remainder of the division. Either he knows it or he has to look up the documentation of bigint to see what is the second element returned by the division. I certainly wouldn't know it unless I looked it up. Writing it like this gives the reader a clue as to what the function returns: my ($quo, $rem) = $bigint->bdiv(10); Again, I don't want to say that this is the correct and only way to do it. Both styles certainly have their merit and can be defended. I agree however that we are somewhat off topic 
|
|
|
|
remotemass (OP)
Legendary
Offline
Activity: 1122
Merit: 1017
ASMR El Salvador
|
 |
March 24, 2023, 06:15:33 AM Last edit: February 25, 2025, 12:31:30 PM by remotemass |
|
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Bitcoin Morse Converter (Prefix-Free with Zero)</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 20px auto; padding: 20px; } h1, h2 { color: #333; } p, li { line-height: 1.6; } label { display: block; margin: 10px 0 5px; } input[type="text"] { width: 100%; padding: 5px; margin-bottom: 10px; } button { padding: 8px 16px; margin-right: 10px; background-color: #007BFF; color: white; border: none; cursor: pointer; } button:hover { background-color: #0056b3; } .section { margin-bottom: 30px; } code { background-color: #f4f4f4; padding: 2px 5px; border-radius: 3px; } .dictionary { column-count: 3; column-gap: 20px; } .dictionary li { margin-bottom: 5px; } </style> </head> <body> <h1>Bitcoin Morse Converter (Prefix-Free with Zero)</h1>
<div class="section"> <h2>Tutorial: Bitcoin Morse with '0' (Revised)</h2> <p>This updated "Bitcoin Morse" protocol supports '0' and ensures unambiguous decoding by using prefix-free codes. It encodes strings (like Bitcoin addresses) into numeric-only format using digits 0-9, covering 0-9, A-Z, and a-z (excluding O, I, l).</p> <h3>Character Set</h3> <p>Supports 57 characters:</p> <ul> <li>Digits: 0-9 (10 characters)</li> <li>Uppercase: A-H, J-N, P-U, V-Z (24 characters)</li> <li>Lowercase: a-h, j-n, p-u, v-z (23 characters)</li> </ul> <p>Example: <code>04jsRJx8HnKq9jyqfBQKgvvnFy3rNCbc3G</code>.</p> <h3>Encoding Rules</h3> <p>Each character has a unique numeric code, designed so no code is a prefix of another:</p> <ul> <li><strong>Digits</strong>: 2 digits, start with '1' to separate from letters.</li> <li><strong>Letters</strong>: Start with '0', vary by length (2, 3, 4, 5 digits) and pattern.</li> </ul> <h3>Complete Dictionary</h3> <p>Here’s every possible character and its Bitcoin Morse code:</p> <ul class="dictionary"> <li><code>0 → 10</code></li> <li><code>1 → 11</code></li> <li><code>2 → 12</code></li> <li><code>3 → 13</code></li> <li><code>4 → 14</code></li> <li><code>5 → 15</code></li> <li><code>6 → 16</code></li> <li><code>7 → 17</code></li> <li><code>8 → 18</code></li> <li><code>9 → 19</code></li> <li><code>A → 01</code></li> <li><code>B → 02</code></li> <li><code>C → 03</code></li> <li><code>D → 04</code></li> <li><code>E → 05</code></li> <li><code>F → 06</code></li> <li><code>G → 07</code></li> <li><code>H → 08</code></li> <li><code>J → 09</code></li> <li><code>K → 001</code></li> <li><code>M → 002</code></li> <li><code>N → 003</code></li> <li><code>P → 004</code></li> <li><code>Q → 005</code></li> <li><code>R → 006</code></li> <li><code>S → 007</code></li> <li><code>T → 008</code></li> <li><code>U → 009</code></li> <li><code>V → 0001</code></li> <li><code>W → 0002</code></li> <li><code>X → 0003</code></li> <li><code>Y → 0004</code></li> <li><code>Z → 0005</code></li> <li><code>a → 021</code></li> <li><code>b → 022</code></li> <li><code>c → 023</code></li> <li><code>d → 024</code></li> <li><code>e → 025</code></li> <li><code>f → 026</code></li> <li><code>g → 027</code></li> <li><code>h → 028</code></li> <li><code>j → 029</code></li> <li><code>k → 0021</code></li> <li><code>m → 0022</code></li> <li><code>n → 0023</code></li> <li><code>p → 0024</code></li> <li><code>q → 0025</code></li> <li><code>r → 0026</code></li> <li><code>s → 0027</code></li> <li><code>t → 0028</code></li> <li><code>u → 0029</code></li> <li><code>v → 00021</code></li> <li><code>w → 00022</code></li> <li><code>x → 00023</code></li> <li><code>y → 00024</code></li> <li><code>z → 00025</code></li> </ul> <h3>Example</h3> <p>For <code>0K0</code>: <ul> <li>"0" → "10"</li> <li>"K" → "001"</li> <li>"0" → "10"</li> </ul> Result: <code>1000110</code> (uniquely decodable). </p> <p>For <code>04js</code>: <ul> <li>"0" → "10"</li> <li>"4" → "14"</li> <li>"j" → "029"</li> <li>"s" → "0027"</li> </ul> Result: <code>10140290027</code>. </p> <h3>Purpose</h3> <p>A numeric-only encoding that’s unambiguous, supports '0', and works for Bitcoin addresses or any string with these characters.</p> </div>
<div class="section"> <h2>Address to Bitcoin Morse</h2> <label for="bitcoinAddress">Enter Address:</label> <input type="text" id="bitcoinAddress" placeholder="e.g., 04jsRJx8HnKq9jyqfBQKgvvnFy3rNCbc3G"> <button onclick="toBitcoinMorse()">Convert to Morse</button> <label for="morseOutput">Bitcoin Morse Result:</label> <input type="text" id="morseOutput" readonly> </div>
<div class="section"> <h2>Bitcoin Morse to Address</h2> <label for="morseInput">Enter Bitcoin Morse:</label> <input type="text" id="morseInput" placeholder="e.g., 10140290027..."> <button onclick="toBitcoinAddress()">Convert to Address</button> <label for="addressOutput">Address Result:</label> <input type="text" id="addressOutput" readonly> </div>
<script> // Encoding map for revised Bitcoin Morse const encodeMap = { '0': '10', '1': '11', '2': '12', '3': '13', '4': '14', '5': '15', '6': '16', '7': '17', '8': '18', '9': '19', 'A': '01', 'B': '02', 'C': '03', 'D': '04', 'E': '05', 'F': '06', 'G': '07', 'H': '08', 'J': '09', 'K': '001', 'M': '002', 'N': '003', 'P': '004', 'Q': '005', 'R': '006', 'S': '007', 'T': '008', 'U': '009', 'V': '0001', 'W': '0002', 'X': '0003', 'Y': '0004', 'Z': '0005', 'a': '021', 'b': '022', 'c': '023', 'd': '024', 'e': '025', 'f': '026', 'g': '027', 'h': '028', 'j': '029', 'k': '0021', 'm': '0022', 'n': '0023', 'p': '0024', 'q': '0025', 'r': '0026', 's': '0027', 't': '0028', 'u': '0029', 'v': '00021', 'w': '00022', 'x': '00023', 'y': '00024', 'z': '00025' };
// Reverse map for decoding const decodeMap = Object.fromEntries(Object.entries(encodeMap).map(([k, v]) => [v, k]));
// Convert address to Bitcoin Morse function toBitcoinMorse() { const address = document.getElementById('bitcoinAddress').value.trim(); let morse = '';
for (let char of address) { if (encodeMap[char]) { morse += encodeMap[char]; } else { alert(`Invalid character: ${char} (O, I, l not supported)`); return; } }
document.getElementById('morseOutput').value = morse; }
// Convert Bitcoin Morse back to address function toBitcoinAddress() { const morse = document.getElementById('morseInput').value.trim(); let address = ''; let i = 0;
while (i < morse.length) { let found = false; for (let len of [5, 4, 3, 2]) { // Check longest to shortest const chunk = morse.substr(i, len); if (decodeMap[chunk]) { address += decodeMap[chunk]; i += len; found = true; break; } } if (!found) { alert(`Invalid Bitcoin Morse sequence at position ${i}: ${morse.substr(i, 5)}`); return; } }
document.getElementById('addressOutput').value = address; } </script> </body> </html> https://cubicpostcode.github.io/bitcoinMorse.html
|
" PetroPayPal ≠ PayPal. Sats > petrocash. Stack free. " — #Cubic #Postcode.
|
|
|
odolvlobo
Legendary
Offline
Activity: 4690
Merit: 3620
|
 |
March 24, 2023, 09:07:48 AM |
|
Your encoding scheme is extremely inefficient. A variable length code is not helpful because all characters have the same frequency.
As others have mentioned, the most efficient scheme would be to convert the 25-byte value to a decimal number. The result is a 61 digit address.
Alternatively, if you want the address encoded directly, you could assign a two-digit number to each symbol.
|
Join an anti-signature campaign: Click ignore on the members of signature campaigns. PGP Fingerprint: 6B6BC26599EC24EF7E29A405EAF050539D0B2925 Signing address: 13GAVJo8YaAuenj6keiEykwxWUZ7jMoSLt
|
|
|
|