From: "Luke-Jr" <luke@dashjr.org>
To: bitcoin-development@lists.sourceforge.net
Subject: [Bitcoin-development] RFC: c32d encoding
Date: Wed, 15 May 2013 06:33:28 +0000 [thread overview]
Message-ID: <201305150633.31085.luke@dashjr.org> (raw)
[-- Attachment #1: Type: text/plain, Size: 1814 bytes --]
https://bitcointalk.org/?topic=205878
This encoding is designed so that it could replace Base58Check in new data,
with the following goals in mind:
- Impossible(?) to manipulate without completely changing it
- Clearly identifiable prefix, regardless of data size
- Cheaper to process (simpler and faster code; it's a power-of-two radix)
- Fixed length string for fixed length data
- More unambiguous (removal of chars 'isuvzSVZ')
- Compatible with using seven-segment displays
- Altcoin friendly (16 bit namespace, which can be read without decoding)
Since there are fewer digits and more identifying/signature characters,
addresses are longer. This should be less of a problem since ordinary users
will hopefully be using addresses less common as the payment protocol becomes
more popular.
Example Python code (including tests) is attached.
I can write up a formal BIP if this seems useful.
For example:
160 bits of data, such as current addresses:
2nc111dhAPE2aUdYAOF88JhLn5jEjbULy4eFe9tyFYFE8
An ordinary P2SH destination, incorporating Greg's "require the hash mid-image
to be relayed" concept (256 bits of data):
2bc511A95e74P13dPb6b5t7yrh12EhC363ayH98n1cFbr3rAHdA49nCcC1G3P71j
The same key in Namecoin:
2nc5119ttL35HPhc3Hh6aHe2tOhF6rdFtAOE1ahFLt9Ecabhcn5FLea5Le71P56C
The example "puzzle" script from the wiki (arbitrary scripting):
2bc311d126acCyAnHAjabeUtOHcr7F811j4UYE6ECtOcbcGGn4O9chAt7O7y2LU9ty9cnG4
An alternative for BIP32 extended public keys (560 bits):
2bc911AcchHheAGFnn9LC6FdF7bOc99APJtcEc46U655JheH6LCr3Y333eFEOtPJ9rj22rEcchHheAGFnn9LC6FdF7bOc99APJtcEc46U655JheH6LCr3YJCtPYea
An alternative for BIP32 extended private keys (552 bits):
2bcb11O77GHdP53FH7Jh44OdEh3rLd4eFr2h7c8rGeErELG18yCy9O7L9LednyHJa5hyeAP77GHdP53FH7Jh44OdEh3rLd4eFr2h7c8rGeErELG18yCyGG5drPF1
[-- Attachment #2: c32.py --]
[-- Type: text/x-python, Size: 1676 bytes --]
# Digits are chosen to be the least ambiguous
# They all have unique seven-segment glyphs, and cannot be easily confused by humans
digits = '123456789abcdehjnrtyACEFGHJLOPUY'
radix = len(digits)
def encode(v):
if not len(v):
return ''
n = 0
bits = 0
o = []
pad = (len(v) * 8) % 5
if pad:
v = b'\0' + v
v = bytearray(v) # For Python 2.7 compatibility
for i in range(len(v) - 1, -1, -1):
n |= v[i] << bits
bits += 8
while bits >= 5:
o.insert(0, digits[n & 0x1f])
n >>= 5
bits -= 5
if i == 0 and pad:
break
return ''.join(o)
def decode(s):
n = 0
bits = 0
o = bytearray()
for i in range(len(s) - 1, -1, -1):
n |= digits.index(s[i]) << bits
bits += 5
while bits >= 8:
o.insert(0, n & 0xff)
n >>= 8
bits -= 8
return bytes(o)
def test():
from math import ceil
assert '' == encode(b'')
for (i, oc) in (
(1, '8'),
(2, '2'),
(3, 'j'),
(4, '4'),
(5, 'Y'),
(6, '8'),
(7, '2'),
(8, 'j'),
(9, '4'),
):
ol = int(ceil(i * 8 / 5.))
try:
inzero = b'\0' * i
inone = b'\xff' * i
ezero = encode(inzero)
eone = encode(inone)
dzero = decode(ezero)
done = decode(eone)
assert ezero == '1' * ol
assert eone == oc + ('Y' * (ol - 1))
assert dzero == inzero
assert done == inone
except AssertionError:
raise AssertionError('Input of length %s failed test' % (i,))
try:
for c in range(1024):
decode('111' + chr(c))
except ValueError:
pass
else:
raise AssertionError('Invalid decode input (%02x) did not throw a ValueError' % (c,))
if __name__ == '__main__':
test()
print("Tests passed")
[-- Attachment #3: c32d.py --]
[-- Type: text/x-python, Size: 5168 bytes --]
import c32
import hashlib
import struct
def _checksum(v):
return hashlib.sha256(hashlib.sha256(v).digest()).digest()[-4:]
'''
String format:
- c32(Raw format) in reverse order
Raw format:
- 4 bytes checksum
- N bytes data (NOTE: encoded to prevent hidden changes)
- - for script:
- - - N bytes: varint preimage data length
- - - N bytes: preimage data
- - - N bytes: script data
- - for BIP32 HD parent key:
- - - 32 bytes: chain code
- - - 33 bytes: parent pubkey
- - for BIP32 serialized key:
- - - 1 byte: depth
- - - 4 bytes: child number
- - - 32 bytes: chain code
- - - One of:
- - - - 32 bytes: private key data
- - - - 33 bytes: public key data
- 1 byte flag (ignored if unknown)
- 1 byte type
- - 01 script (with preimage data)
- - 02 script hash preimage
- - 03 BIP32 HD parent key
- - 04 BIP32 serialized public key
- 2 bytes namespace (blockchain id)
- - 2d41 Bitcoin ('2bc')
- - 2e01 Namecoin ('2nc')
- - 2e37 Freicoin ('FRC')
'''
class c32d:
__slots__ = ('data', 'ns', 'dtype', 'dflag')
def __init__(self, data, ns, dtype, dflag):
self.data = data
self.ns = ns
self.dtype = dtype
self.dflag = dflag
@classmethod
def decode(cls, s, raw = False):
if not raw:
full = c32.decode(s[::-1])
else:
full = s
csum = bytearray(full[:4])
v = bytearray(full[4:])
# Encode the configuration bytes to simplify decoding
pv = 0xbf
for i in range(len(v) - 1, len(v) - 5, -1):
pv = v[i] ^ (csum[i % 4]) ^ pv
v[i] = pv
v.append(0xbf)
for i in range(len(v) - 1):
v[i] ^= csum[i % 4] ^ v[i + 1]
v.pop()
v = bytes(v)
if csum != _checksum(v):
raise ValueError('c32d checksum wrong')
o = cls(None, None, None, None)
o.data = v[:-4]
o.dflag = v[-4]
o.dtype = v[-3]
o.ns = struct.unpack('!H', v[-2:])[0]
return o
def encode(self, raw = False):
try:
v = self.data + struct.pack('!BBH', self.dflag, self.dtype, self.ns)
except struct.error as e:
raise ValueError(e)
csum = bytearray(_checksum(v))
v = bytearray(v)
pv = 0xbf
for i in range(len(v) - 1, -1, -1):
pv = v[i] ^ csum[i % 4] ^ pv
if i < len(v) - 4:
v[i] = pv
v = csum + bytes(v)
if raw:
return v
return c32.encode(v)[::-1]
decode = c32d.decode
def encode(*a, **ka):
return c32d(*a, **ka).encode()
def test():
c32.test()
for (p, s, raw) in (
((b'', 0, 0, 0), '1111115Fd9acc', b'\xb5\xa5\x0c\xb9\x00\x00\x00\x00'),
((b'test', 4232, 142, 219), '955OGe8hOGc97hH4EJj1', b'?V\x1e\\d/\x1cq\xdb\x8e\x10\x88'),
((b'\xff' * 0x100, 0xffff, 0xff, 0xff), 'YYYYYYc327OYcC6F9Or6r14UYCJtc5UGt9n2YYbeH63jda5GnYjCEO3r8E53dGYFbchrG4c327OYcC6F9Or6r14UYCJtc5UGt9n2YYbeH63jda5GnYjCEO3r8E53dGYFbchrG4c327OYcC6F9Or6r14UYCJtc5UGt9n2YYbeH63jda5GnYjCEO3r8E53dGYFbchrG4c327OYcC6F9Or6r14UYCJtc5UGt9n2YYbeH63jda5GnYjCEO3r8E53dGYFbchrG4c327OYcC6F9Or6r14UYCJtc5UGt9n2YYbeH63jda5GnYjCEO3r8E53dGYFbchrG4c327OYcC6F9Or6r14UYCJtc5UGt9n2YYbeH63jda5GnYjCEO3r8E53dGYFbchrG4c327OYcC6F9Or6r14UYCJtc5UGb2cOdG3', b'\xb0\xce,*\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xc7\x88\xb9j\xbf\xf0\xc1\x12\xff\xff\xff\xff'),
):
(kp, pp) = ({}, p)
for i in range(2):
o = c32d(*pp, **kp)
assert o.data == p[0]
assert o.ns == p[1]
assert o.dtype == p[2]
assert o.dflag == p[3]
kp = {
'data': p[0],
'ns': p[1],
'dtype': p[2],
'dflag': p[3],
}
pp = ()
assert o.encode() == s
assert o.encode(raw=True) == raw
def ensureValueError(f):
try:
f()
except ValueError:
pass
else:
raise AssertionError('Invalid decode input did not throw a ValueError')
ensureValueError(lambda: encode(b'', -1, 0, 0))
ensureValueError(lambda: encode(b'', 0x10000, 0, 0))
ensureValueError(lambda: encode(b'', 0, -1, 0))
ensureValueError(lambda: encode(b'', 0, 0x100, 0))
ensureValueError(lambda: encode(b'', 0, 0, -1))
ensureValueError(lambda: encode(b'', 0, 0, 0x100))
# Invalid c32d
ensureValueError(lambda: decode('1111115Fd9adc'))
ensureValueError(lambda: decode('11A1115Fd9acc'))
# Invalid c32
ensureValueError(lambda: decode('111x115Fd9acc'))
ensureValueError(lambda: decode('1111115Fd9acx'))
if __name__ == '__main__':
test()
print("Tests passed")
reply other threads:[~2013-05-15 6:33 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=201305150633.31085.luke@dashjr.org \
--to=luke@dashjr.org \
--cc=bitcoin-development@lists.sourceforge.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox