I'd like to propose an extension to the identity protocol that would give a secure unique mapping from your MPK (master public key) to a short, pronounceable, memorable name. It's simply an encoding of a transactions coordinates in the blockchain into a list of phonemes. For example (code below), in block 264192:
tx name
1 ~dus
2 ~mirlep
50 ~harbyr-salmev
8000 ~larryt-norbel
1000000 ~nisneb-neb-rondef
(Last two don't exist in the blockchain, but if they did, these would be their names.)
Motivation:
A very convenient key fingerprint to help with key verification.
For those weird enough to make these their commonly used name (hopefully not in meatspace), key verification is automatic. These names are sufficiently dissimilar that problems associated with using strings as identities don't exist (I want to do some measurements still). Using them this way leverages our monkey brain's natural ability to conceptualize social networks and reputation using memorable names. Of course all of this assumes the monkeys can communicate over multiple independent channels so that a single operator can't replace all the names that are passed around with ones whose keys he holds.
The intention is for certain groups of weirdos to avoid needing a PKI.
Draft proposed standard:
(I'll do a nice job of writing this up if there's any interest, or after I receive some feedback. Should it go on the wiki?)
Basic rules:
- Transactions take 100 blocks to mature to ensure their blockchain coordinates are unmalleable.
- Names expire after 262144 blocks (~5 years) because it's a good idea to periodically retire keys.
- Names are recycled after 524288 blocks (~10 years) because dead names must be freed up for future use. Notice the 5 year safety window between name expiry and reuse.
- 8 bit names created every 2048 blocks (~once every 2 weeks) as the first transaction after the coinbase.
- 16 bit names created every 8 blocks as the second transaction after the coinbase.
- 24 bit names associated with the first 32 transactions after the coinbase (but are trumped by the shorter names).
- 32 bit names associated with the first 8192 transactions after the 32nd.
- 40 bit names associated with the first 2097152 transactions after the 8224th.
- The shorter names might create a kind of valuable "blockchain real estate" that miners could earn extra revenue from.
- Different phoneme sets (and alphabets) can simultaneously be used as long as they respect backwards-compatibility to avoid collisions.
- I used phonemes from Urbit:
Onset phonemes:
doz mar bin wan sam lit sig hid fid lis sog dir wac sab wis sib
rig sol dop mod fog lid hop dar dor lor hod fol rin tog sil mir
hol pas lac rov liv dal sat lib tab han tic pid tor bol fos dot
los dil for pil ram tir win tad bic dif roc wid bis das mid lop
ril nar dap mol san loc nov sit nid tip sic rop wit nat pan min
rit pod mot tam tol sav pos nap nop som fin fon ban por wor sip
ron nor bot wic soc wat dol mag pic dav bid bal tim tas mal lig
siv tag pad sal div dac tan sid fab tar mon ran nis wol mis pal
las dis map rab tob rol lat lon nod nav fig nom nib pag sop ral
bil had doc rid moc pac rav rip fal tod til tin hap mic fan pat
tac lab mog sim son pin lom ric tap fir has bos bat poc hac tid
hav sap lin dib hos dab bit bar rac par lod dos bor toc hil mac
tom dig fil fas mit hob har mig hin rad mas hal rag lag fad top
mop hab nil nos mil fop fam dat nol din hat nac ris fot rib hoc
nim lar fit wal rap sar nal mos lan don dan lad dov riv bac pol
lap tal pit nam bon ros ton fod pon sov noc sor lav mat mip fap
Coda phonemes:
zod nec bud wes sev per sut let ful pen syt dur wep ser wyl sun
ryp syx dyr nup heb peg lup dep dys put lug hec ryt tyv syd nex
lun mep lut sep pes del sul ped tem led tul met wen byn hex feb
pyl dul het mev rut tyl wyd tep bes dex sef wyc bur der nep pur
rys reb den nut sub pet rul syn reg tyd sup sem wyn rec meg net
sec mul nym tev web sum mut nyx rex teb fus hep ben mus wyx sym
sel ruc dec wex syr wet dyl myn mes det bet bel tux tug myr pel
syp ter meb set dut deg tex sur fel tud nux rux ren wyt nub med
lyt dus neb rum tyn seg lyx pun res red fun rev ref mec ted rus
bex leb dux ryn num pyx ryg ryx fep tyr tus tyc leg nem fer mer
ten lus nus syl tec mex pub rym tuc fyl lep deb ber mug hut tun
byl sud pem dev lur def bus bep run mel pex dyt byt typ lev myl
wed duc fur fex nul luc len ner lex rup ned lec ryd lyd fen wel
nyd hus rel rud nes hes fet des ret dun ler nyr seb hul ryl lud
rem lys fyn wer ryc sug nys nyl lyn dyn dem lux fed sed bec mun
lyr tes mud nyt byr sen weg fyr mur tel rep teg pec nel nev fes
In more detail (Rough draft! Please chime in with any advice!):
import binascii
import hashlib
# (un)hexlify to/from unicode, needed for Python3
unhexlify = binascii.unhexlify
hexlify = binascii.hexlify
if sys.version > '3':
unhexlify = lambda h: binascii.unhexlify(h.encode('utf8'))
hexlify = lambda b: binascii.hexlify(b).decode('utf8')
class Swizzler(object):
''' Balanced Feistel network. Operates on bit strings of even length. Key expansion and round function based on SHA256. '''
def __init__(self, key=None, rounds=20):
key = unhexlify(''.zfill(64)) if key is None else key
K = [hashlib.sha256(key).digest()]
for i in xrange(rounds):
K.append(hashlib.sha256((K[-1])).digest())
self._K = K
self.r = rounds
def _F(self, i, x, m):
x = self._K[i] + unhexlify(hex(x)[2:].zfill(512))
x = int(hashlib.sha256(x).hexdigest(), 16)
return int(x & m)
def E(self, b):
assert len(b) & 1 == 0
n = len(b)/2
m = (1 << n) - 1
x = int(b, 2)
R = x & m
L = x >> n
for i in xrange(self.r):
tmp = R
R = self._F(i, R, m) ^ L
L = tmp
return bin((R << n) + L)[2:].zfill(2*n)
def D(self, b):
assert len(b) & 1 == 0
n = len(b)/2
m = (1 << n) - 1
x = int(b, 2)
L = x & m
R = x >> n
for i in reversed(xrange(self.r)):
tmp = L
L = self._F(i, L, m) ^ R
R = tmp
return bin((L << n) + R)[2:].zfill(2*n)
class BlockchainNamespace(object):
''' Martian namespace for transactions in the Bitcoin blockchain. '''
def __init__(self):
self.swizzler = Swizzler()
self.sis = ['doz', 'mar', 'bin', 'wan', 'sam', 'lit', 'sig', 'hid', 'fid', 'lis', 'sog', 'dir', 'wac', 'sab', 'wis', 'sib', 'rig', 'sol', 'dop', 'mod', 'fog', 'lid', 'hop', 'dar', 'dor', 'lor', 'hod', 'fol', 'rin', 'tog', 'sil', 'mir', 'hol', 'pas', 'lac', 'rov', 'liv', 'dal', 'sat', 'lib', 'tab', 'han', 'tic', 'pid', 'tor', 'bol', 'fos', 'dot', 'los', 'dil', 'for', 'pil', 'ram', 'tir', 'win', 'tad', 'bic', 'dif', 'roc', 'wid', 'bis', 'das', 'mid', 'lop', 'ril', 'nar', 'dap', 'mol', 'san', 'loc', 'nov', 'sit', 'nid', 'tip', 'sic', 'rop', 'wit', 'nat', 'pan', 'min', 'rit', 'pod', 'mot', 'tam', 'tol', 'sav', 'pos', 'nap', 'nop', 'som', 'fin', 'fon', 'ban', 'por', 'wor', 'sip', 'ron', 'nor', 'bot', 'wic', 'soc', 'wat', 'dol', 'mag', 'pic', 'dav', 'bid', 'bal', 'tim', 'tas', 'mal', 'lig', 'siv', 'tag', 'pad', 'sal', 'div', 'dac', 'tan', 'sid', 'fab', 'tar', 'mon', 'ran', 'nis', 'wol', 'mis', 'pal', 'las', 'dis', 'map', 'rab', 'tob', 'rol', 'lat', 'lon', 'nod', 'nav', 'fig', 'nom', 'nib', 'pag', 'sop', 'ral', 'bil', 'had', 'doc', 'rid', 'moc', 'pac', 'rav', 'rip', 'fal', 'tod', 'til', 'tin', 'hap', 'mic', 'fan', 'pat', 'tac', 'lab', 'mog', 'sim', 'son', 'pin', 'lom', 'ric', 'tap', 'fir', 'has', 'bos', 'bat', 'poc', 'hac', 'tid', 'hav', 'sap', 'lin', 'dib', 'hos', 'dab', 'bit', 'bar', 'rac', 'par', 'lod', 'dos', 'bor', 'toc', 'hil', 'mac', 'tom', 'dig', 'fil', 'fas', 'mit', 'hob', 'har', 'mig', 'hin', 'rad', 'mas', 'hal', 'rag', 'lag', 'fad', 'top', 'mop', 'hab', 'nil', 'nos', 'mil', 'fop', 'fam', 'dat', 'nol', 'din', 'hat', 'nac', 'ris', 'fot', 'rib', 'hoc', 'nim', 'lar', 'fit', 'wal', 'rap', 'sar', 'nal', 'mos', 'lan', 'don', 'dan', 'lad', 'dov', 'riv', 'bac', 'pol', 'lap', 'tal', 'pit', 'nam', 'bon', 'ros', 'ton', 'fod', 'pon', 'sov', 'noc', 'sor', 'lav', 'mat', 'mip', 'fap']
self.dex = ['zod', 'nec', 'bud', 'wes', 'sev', 'per', 'sut', 'let', 'ful', 'pen', 'syt', 'dur', 'wep', 'ser', 'wyl', 'sun', 'ryp', 'syx', 'dyr', 'nup', 'heb', 'peg', 'lup', 'dep', 'dys', 'put', 'lug', 'hec', 'ryt', 'tyv', 'syd', 'nex', 'lun', 'mep', 'lut', 'sep', 'pes', 'del', 'sul', 'ped', 'tem', 'led', 'tul', 'met', 'wen', 'byn', 'hex', 'feb', 'pyl', 'dul', 'het', 'mev', 'rut', 'tyl', 'wyd', 'tep', 'bes', 'dex', 'sef', 'wyc', 'bur', 'der', 'nep', 'pur', 'rys', 'reb', 'den', 'nut', 'sub', 'pet', 'rul', 'syn', 'reg', 'tyd', 'sup', 'sem', 'wyn', 'rec', 'meg', 'net', 'sec', 'mul', 'nym', 'tev', 'web', 'sum', 'mut', 'nyx', 'rex', 'teb', 'fus', 'hep', 'ben', 'mus', 'wyx', 'sym', 'sel', 'ruc', 'dec', 'wex', 'syr', 'wet', 'dyl', 'myn', 'mes', 'det', 'bet', 'bel', 'tux', 'tug', 'myr', 'pel', 'syp', 'ter', 'meb', 'set', 'dut', 'deg', 'tex', 'sur', 'fel', 'tud', 'nux', 'rux', 'ren', 'wyt', 'nub', 'med', 'lyt', 'dus', 'neb', 'rum', 'tyn', 'seg', 'lyx', 'pun', 'res', 'red', 'fun', 'rev', 'ref', 'mec', 'ted', 'rus', 'bex', 'leb', 'dux', 'ryn', 'num', 'pyx', 'ryg', 'ryx', 'fep', 'tyr', 'tus', 'tyc', 'leg', 'nem', 'fer', 'mer', 'ten', 'lus', 'nus', 'syl', 'tec', 'mex', 'pub', 'rym', 'tuc', 'fyl', 'lep', 'deb', 'ber', 'mug', 'hut', 'tun', 'byl', 'sud', 'pem', 'dev', 'lur', 'def', 'bus', 'bep', 'run', 'mel', 'pex', 'dyt', 'byt', 'typ', 'lev', 'myl', 'wed', 'duc', 'fur', 'fex', 'nul', 'luc', 'len', 'ner', 'lex', 'rup', 'ned', 'lec', 'ryd', 'lyd', 'fen', 'wel', 'nyd', 'hus', 'rel', 'rud', 'nes', 'hes', 'fet', 'des', 'ret', 'dun', 'ler', 'nyr', 'seb', 'hul', 'ryl', 'lud', 'rem', 'lys', 'fyn', 'wer', 'ryc', 'sug', 'nys', 'nyl', 'lyn', 'dyn', 'dem', 'lux', 'fed', 'sed', 'bec', 'mun', 'lyr', 'tes', 'mud', 'nyt', 'byr', 'sen', 'weg', 'fyr', 'mur', 'tel', 'rep', 'teg', 'pec', 'nel', 'nev', 'fes']
self.sis_index = {self.sis[i]:i for i in range(len(self.sis))}
self.dex_index = {self.dex[i]:i for i in range(len(self.dex))}
def coords_to_vint16(self, tx_height, block_height, block_count):
if block_count < block_height + 100:
raise Exception("Transaction hasn't matured yet!")
# Names expire after 262144 blocks (~5 years).
if (block_count - block_height) >> 18 > 0:
raise Exception("Transaction's name has expired!")
if tx_height <= 0 or tx_height > 2097152:
raise Exception('Transaction out of range!')
h = block_height
t = tx_height
# 8 bit names created every 2048 blocks (~once every 2 weeks).
if h & 2047 == 0 and t == 1:
# Names are recycled after 524288 blocks (~10 years).
b = bin((h & 524287) >> 11)[2:].zfill(8)
# 16 bit names created every 8 blocks.
elif h & 7 == 0 and t == 2:
b = bin((h & 524287) >> 3)[2:].zfill(16)
# 24 bit names created 32/block.
elif (t-1) >> 5 == 0:
b = bin(((t-1) << 19) + (h & 524287))[2:].zfill(24)
# 32 bit names created 8192/block.
elif (t-33) >> 13 == 0:
b = bin(((t-33) << 19) + (h & 524287))[2:].zfill(32)
# 40 bit names created 2097152/block.
elif (t-8225) >> 21 == 0:
b = bin(((t-8225) << 19) + (h & 524287))[2:].zfill(40)
else:
raise Exception
# Randomized 1-1 mapping to minimize similarities between consecutive names.
b = self.swizzler.E(b)
return [int(b[8*i:8*(i+1)], 2) for i in range(len(b)/8)]
def vint16_to_coords(self, vint16, block_count):
b = ''.join([bin(n)[2:].zfill(8) for n in vint16])
b = self.swizzler.D(b)
n = int(b, 2)
if len(b) == 8:
t = 1
h = n << 11
elif len(b) == 16:
t = 2
h = n << 3
elif len(b) == 24:
t = (n >> 19) + 1
h = n & 524287
elif len(b) == 32:
t = (n >> 19) + 33
h = n & 524287
elif len(b) == 40:
t = (n >> 19) + 8225
h = n & 524287
assert block_count >= h + 100
while (block_count - h) / 524288 > 0:
h += 524288
return (t, h)
def encode(self, tx_height, block_height, block_count):
v = self.coords_to_vint16(tx_height, block_height, block_count)
name = []
for i in range(0,len(v)-1,2):
name.append(self.sis[v[i]] + self.dex[v[i+1]])
if len(v) & 1 == 1:
name.insert(abs(len(v)/2-1), self.dex[v[-1]])
return '~' + '-'.join(name)
def decode(self, name, block_count):
v = []
end = None
for w in name.lstrip('~').split('-'):
if len(w) == 3:
end = w
else:
v.append(self.sis_index[w[:3]])
v.append(self.dex_index[w[3:]])
if end:
v.append(self.dex_index[end])
return self.vint16_to_coords(v, block_count)