From c26b8124e58eb8f6d6c6fe56a91e0f40aa44c752 Mon Sep 17 00:00:00 2001 From: Tom Ritchford Date: Fri, 17 Apr 2015 16:41:27 -0400 Subject: [PATCH] Add original manifest.py. --- bin/python/ripple/manifest.py | 204 +++++++++++++++++++++++++++++ bin/python/ripple/test_manifest.py | 85 ++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 bin/python/ripple/manifest.py create mode 100644 bin/python/ripple/test_manifest.py diff --git a/bin/python/ripple/manifest.py b/bin/python/ripple/manifest.py new file mode 100644 index 000000000..0b7a37d92 --- /dev/null +++ b/bin/python/ripple/manifest.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python + +import base64, os, random, sys +import ed25519 +import ecdsa +from hashlib import sha256 +from ripple.util.Encode import base58encode, base58decode + +#----------------------------------------------------------- + +# +# RippleAddress library +# +# Human strings are base-58 with a +# version prefix and a checksum suffix. +# + +VER_NONE = 1 +VER_NODE_PUBLIC = 28 +VER_NODE_PRIVATE = 32 +VER_ACCOUNT_ID = 0 +VER_ACCOUNT_PUBLIC = 35 +VER_ACCOUNT_PRIVATE = 34 +VER_FAMILY_GENERATOR = 41 +VER_FAMILY_SEED = 33 + +def ra_check(b): + """.Returns a 4-byte checksum of a binary.""" + return sha256(sha256(b).digest()).digest()[:4] + +def ra_encode(ver, b): + """Encodes a binary as human string.""" + b = chr(ver) + b + return base58encode(b + ra_check(b)) + +def ra_decode(s): + """Decodes a human base-58 string into its version and binary.""" + b = base58decode(s) + check = b[-4:] + if (check != ra_check(b[:-4])): + raise ValueError('bad checksum') + ver = ord(b[0]) + b = b[1:-4] + return ver, b + +def field_code(kind, name): + s = "" + if (kind < 16): + if (name < 16): + s += chr((kind << 4) + name) + else: + s += chr(kind << 4) + s += chr(name) + elif (name < 16): + s += chr(name) + s += chr(kind) + else: + s += '\0' + s += chr(kind) + s += chr(name) + return s + +#----------------------------------------------------------- + +STI_UINT32 = 2 +STI_VL = 7 + +sfSequence = field_code(STI_UINT32, 4) +sfPublicKey = field_code(STI_VL, 1) +sfSigningPubKey = field_code(STI_VL, 3) +sfSignature = field_code(STI_VL, 6) + +def to_bytes(n, length, endianess='big'): + h = '%x' % n + s = ('0'*(len(h) % 2) + h).zfill(length*2).decode('hex') + return s if endianess == 'big' else s[::-1] + +def lenvl(b): + s = '' + n = len(b) + if (n < 192): + s += chr(n) + return s + raise Exception('too long') + +def strvl(b): + return lenvl(b) + b + +def str32(n): + return to_bytes(n, 4) + +#----------------------------------------------------------- + +def gen_seed(urandom=os.urandom): + seed = urandom(16) + return seed + +def gen_ed(urandom=os.urandom): + sk = urandom(32) + pk = ed25519.publickey(sk) + return sk, pk + +def gen_ec(): + # Can't be unit tested easily. + sk = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1) + vk = sk.get_verifying_key() + sig = sk.sign("message") + assert vk.verify(sig, "message") + return sk, vk + +def gen_manifest(pk, vpk, seq): + s = "" + s += sfSequence + s += str32(seq) + s += sfPublicKey + s += strvl(pk) + s += sfSigningPubKey + s += strvl(vpk) + return s + +def sign_manifest(m, sk, pk): + s = "MAN\0" + s += m + sig = ed25519.signature(s, sk, pk) + m += sfSignature + strvl(sig) + return m + +#----------------------------------------------------------- + +def wrap(s, cols = 60): + n = len(s) + if (n <= cols): + return s + l = (n + cols - 1) / cols + w = n / l + while(l > 0): + s = s[:w * l] + '\n' + s[w*l:] + l -= 1 + return s + +#----------------------------------------------------------- + +if __name__ == '__main__': + if (len(sys.argv) == 2 and sys.argv[1] == 'create'): + sk, pk = gen_ed() + apk = chr(0xed) + pk + pkh = ra_encode(VER_NODE_PUBLIC, apk) + skh = ra_encode(VER_NODE_PRIVATE, sk) + v0, apk0 = ra_decode(pkh) + assert v0 == VER_NODE_PUBLIC + assert apk0 == apk + print ("[validators]") + print (pkh) + print + print ("[master_secret]") + print (skh) + exit() + + if (len(sys.argv) == 3 and sys.argv[1] == 'check'): + ver, b = ra_decode(sys.argv[2]) + print ('ver = ' + str(ver)) + print ('len = ' + str(len(b))) + h = ra_encode(ver, b) + print (h) + assert h == sys.argv[2] + exit() + + if (len(sys.argv) == 5 and sys.argv[1] == 'sign'): + seq = int(sys.argv[2]) + vpkh = sys.argv[3] + skh = sys.argv[4] + try: + v, avpk = ra_decode(vpkh) + if (v != VER_NODE_PUBLIC or + len(avpk) != 33 or + (ord(avpk[0]) != 2 and ord(avpk[0]) != 3)): + raise ValueError() + except ValueError: + print ("Bad validator-public: " + vpkh) + exit() + try: + v, sk = ra_decode(skh) + if (v != VER_NODE_PRIVATE or + len(sk) != 32): + raise ValueError() + except ValueError: + print ("Bad master-secret: " + skh) + pk = ed25519.publickey(sk) + apk = chr(0xed) + pk + m = gen_manifest(apk, avpk, seq) + m1 = sign_manifest(m, sk, pk) + print ('[validation_manifest]') + print wrap(base64.b64encode(m1)) + exit() + + print("""\ + Usage: + create + Create a new master public/secret key pair. + + sign + Create a new signed manifest with the given sequence + number, validator public key, and master secret key. + """) diff --git a/bin/python/ripple/test_manifest.py b/bin/python/ripple/test_manifest.py new file mode 100644 index 000000000..740afd4fe --- /dev/null +++ b/bin/python/ripple/test_manifest.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function + +from ripple import manifest + +from unittest import TestCase + +BINARY = 'nN9kfUnKTf7PpgLG' + +class test_manifest(TestCase): + def test_check(self): + self.assertEquals(manifest.ra_check(BINARY), '\xaa\xaar\x9d') + + def test_encode(self): + self.assertEquals( + manifest.ra_encode(manifest.VER_ACCOUNT_PUBLIC, BINARY), + 'sB49XwJgmdEZDo8LmYwki7FYkiaN7') + + def test_decode(self): + ver, b = manifest.ra_decode('sB49XwJgmdEZDo8LmYwki7FYkiaN7') + self.assertEquals(ver, manifest.VER_ACCOUNT_PUBLIC) + self.assertEquals(b, BINARY) + + def test_field_code(self): + self.assertEquals(manifest.field_code(manifest.STI_UINT32, 4), '$') + self.assertEquals(manifest.field_code(manifest.STI_VL, 1), 'q') + self.assertEquals(manifest.field_code(manifest.STI_VL, 3), 's') + self.assertEquals(manifest.field_code(manifest.STI_VL, 6), 'v') + + def test_to_bytes(self): + self.assertEquals( + manifest.to_bytes(12345, 16, endianess='big'), + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0009') + + self.assertEquals( + manifest.to_bytes(12345, 16, endianess='not big'), + '90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + + def test_strvl(self): + self.assertEquals(manifest.strvl(BINARY), '\x10nN9kfUnKTf7PpgLG') + + def test_to_str32(self): + self.assertEquals(manifest.str32(12345), '\x00\x0009') + + def urandom(self, bytes): + return '\5' * bytes + + def test_gen_seed(self): + self.assertEquals(manifest.gen_seed(self.urandom), + '\5\5\5\5\5\5\5\5\5\5\5\5\5\5\5\5') + + def test_gen_ed(self): + private, public = manifest.gen_ed(self.urandom) + self.assertEquals(private, + '\5\5\5\5\5\5\5\5\5\5\5\5\5\5\5\5' + '\5\5\5\5\5\5\5\5\5\5\5\5\5\5\5\5') + self.assertEquals(public, + 'nz\x1c\xdd)\xb0\xb7\x8f\xd1:\xf4\xc5Y\x8f\xef\xf4' + '\xef*\x97\x16n<\xa6\xf2\xe4\xfb\xfc\xcd\x80P[\xf1') + + def test_gen_manifest(self): + _, pk = manifest.gen_ed(self.urandom) + m = manifest.gen_manifest(pk, 'verify', 12345) + self.assertEquals( + m, '$\x00\x0009q nz\x1c\xdd)\xb0\xb7\x8f\xd1:\xf4\xc5Y\x8f\xef\xf4' + '\xef*\x97\x16n<\xa6\xf2\xe4\xfb\xfc\xcd\x80P[\xf1s\x06verify') + + def test_sign_manifest(self): + sk, pk = manifest.gen_ed(self.urandom) + s = manifest.sign_manifest('manifest', sk, pk) + self.assertEquals( + s, 'manifestv@\xe5\x84\xbe\xc4\x80N\xa0v"\xb2\x80A\x88\x06\xc0' + '\xd2\xbae\x92\x89\xa8\'!\xdd\x00\x88\x06s\xe0\xf74\xe3Yg\xad{$' + '\x17\xd3\x99\xaa\x16\xb0\xeaZ\xd7]\r\xb3\xdc\x1b\x8f\xc1Z\xdfHU' + '\xb5\x92\xac\x82jI\x02') + + def test_wrap(self): + wrap = lambda s: manifest.wrap(s, 5) + self.assertEquals(wrap(''), '') + self.assertEquals(wrap('12345'), '12345') + self.assertEquals(wrap('123456'), '123\n456\n') + self.assertEquals(wrap('12345678'), '1234\n5678\n') + self.assertEquals(wrap('1234567890'), '12345\n67890\n') + self.assertEquals(wrap('12345678901'), '123\n456\n789\n01') + # TOD: there seems to be a carriage return added randomly + # to the last character.