mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 03:26:01 +00:00
Productionize manifest generator.
* Better output. * Better error checking and reporting. * Clearer names. * Python style.
This commit is contained in:
1
bin/manifest
Symbolic link
1
bin/manifest
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
python/Manifest.py
|
||||||
7
bin/python/Manifest.py
Executable file
7
bin/python/Manifest.py
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from ripple.util import Sign
|
||||||
|
|
||||||
|
result = Sign.run_command(sys.argv[1:])
|
||||||
|
exit(0 if result else -1)
|
||||||
51
bin/python/ripple/ledger/SField.py
Normal file
51
bin/python/ripple/ledger/SField.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Constants from ripple/protocol/SField.h
|
||||||
|
|
||||||
|
# special types
|
||||||
|
STI_UNKNOWN = -2
|
||||||
|
STI_DONE = -1
|
||||||
|
STI_NOTPRESENT = 0
|
||||||
|
|
||||||
|
# # types (common)
|
||||||
|
STI_UINT16 = 1
|
||||||
|
STI_UINT32 = 2
|
||||||
|
STI_UINT64 = 3
|
||||||
|
STI_HASH128 = 4
|
||||||
|
STI_HASH256 = 5
|
||||||
|
STI_AMOUNT = 6
|
||||||
|
STI_VL = 7
|
||||||
|
STI_ACCOUNT = 8
|
||||||
|
# 9-13 are reserved
|
||||||
|
STI_OBJECT = 14
|
||||||
|
STI_ARRAY = 15
|
||||||
|
|
||||||
|
# types (uncommon)
|
||||||
|
STI_UINT8 = 16
|
||||||
|
STI_HASH160 = 17
|
||||||
|
STI_PATHSET = 18
|
||||||
|
STI_VECTOR256 = 19
|
||||||
|
|
||||||
|
# high level types
|
||||||
|
# cannot be serialized inside other types
|
||||||
|
STI_TRANSACTION = 10001
|
||||||
|
STI_LEDGERENTRY = 10002
|
||||||
|
STI_VALIDATION = 10003
|
||||||
|
STI_METADATA = 10004
|
||||||
|
|
||||||
|
def field_code(sti, name):
|
||||||
|
if sti < 16:
|
||||||
|
if name < 16:
|
||||||
|
bytes = [(sti << 4) + name]
|
||||||
|
else:
|
||||||
|
bytes = [sti << 4, name]
|
||||||
|
elif name < 16:
|
||||||
|
bytes = [name, sti]
|
||||||
|
else:
|
||||||
|
bytes = [0, sti, name]
|
||||||
|
return ''.join(chr(i) for i in bytes)
|
||||||
|
|
||||||
|
# Selected constants from SField.cpp
|
||||||
|
|
||||||
|
sfSequence = field_code(STI_UINT32, 4)
|
||||||
|
sfPublicKey = field_code(STI_VL, 1)
|
||||||
|
sfSigningPubKey = field_code(STI_VL, 3)
|
||||||
|
sfSignature = field_code(STI_VL, 6)
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
#!/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 <sequence> <validator-public> <master-secret>
|
|
||||||
Create a new signed manifest with the given sequence
|
|
||||||
number, validator public key, and master secret key.
|
|
||||||
""")
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
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.
|
|
||||||
94
bin/python/ripple/util/Base58.py
Normal file
94
bin/python/ripple/util/Base58.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from hashlib import sha256
|
||||||
|
|
||||||
|
#
|
||||||
|
# Human strings are base-58 with a
|
||||||
|
# version prefix and a checksum suffix.
|
||||||
|
#
|
||||||
|
# Copied from ripple/protocol/RippleAddress.h
|
||||||
|
#
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
ALPHABET = 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz'
|
||||||
|
|
||||||
|
VERSION_NAME = {
|
||||||
|
VER_NONE: 'VER_NONE',
|
||||||
|
VER_NODE_PUBLIC: 'VER_NODE_PUBLIC',
|
||||||
|
VER_NODE_PRIVATE: 'VER_NODE_PRIVATE',
|
||||||
|
VER_ACCOUNT_ID: 'VER_ACCOUNT_ID',
|
||||||
|
VER_ACCOUNT_PUBLIC: 'VER_ACCOUNT_PUBLIC',
|
||||||
|
VER_ACCOUNT_PRIVATE: 'VER_ACCOUNT_PRIVATE',
|
||||||
|
VER_FAMILY_GENERATOR: 'VER_FAMILY_GENERATOR',
|
||||||
|
VER_FAMILY_SEED: 'VER_FAMILY_SEED'
|
||||||
|
}
|
||||||
|
|
||||||
|
class Alphabet(object):
|
||||||
|
def __init__(self, radix, digit_to_char, char_to_digit):
|
||||||
|
self.radix = radix
|
||||||
|
self.digit_to_char = digit_to_char
|
||||||
|
self.char_to_digit = char_to_digit
|
||||||
|
|
||||||
|
def transcode_from(self, s, source_alphabet):
|
||||||
|
n, zero_count = source_alphabet._digits_to_number(s)
|
||||||
|
digits = []
|
||||||
|
while n > 0:
|
||||||
|
n, digit = divmod(n, self.radix)
|
||||||
|
digits.append(self.digit_to_char(digit))
|
||||||
|
|
||||||
|
s = ''.join(digits)
|
||||||
|
return self.digit_to_char(0) * zero_count + s[::-1]
|
||||||
|
|
||||||
|
def _digits_to_number(self, digits):
|
||||||
|
stripped = digits.lstrip(self.digit_to_char(0))
|
||||||
|
n = 0
|
||||||
|
for d in stripped:
|
||||||
|
n *= self.radix
|
||||||
|
n += self.char_to_digit(d)
|
||||||
|
return n, len(digits) - len(stripped)
|
||||||
|
|
||||||
|
_INVERSE_INDEX = dict((c, i) for (i, c) in enumerate(ALPHABET))
|
||||||
|
|
||||||
|
# In base 58 encoding, the digits come from the ALPHABET string.
|
||||||
|
BASE58 = Alphabet(len(ALPHABET), ALPHABET.__getitem__, _INVERSE_INDEX.get)
|
||||||
|
|
||||||
|
# In base 256 encoding, each digit is just a character between 0 and 255.
|
||||||
|
BASE256 = Alphabet(256, chr, ord)
|
||||||
|
|
||||||
|
def encode(b):
|
||||||
|
return BASE58.transcode_from(b, BASE256)
|
||||||
|
|
||||||
|
def decode(b):
|
||||||
|
return BASE256.transcode_from(b, BASE58)
|
||||||
|
|
||||||
|
def checksum(b):
|
||||||
|
"""Returns a 4-byte checksum of a binary."""
|
||||||
|
return sha256(sha256(b).digest()).digest()[:4]
|
||||||
|
|
||||||
|
def encode_version(ver, b):
|
||||||
|
"""Encodes a version encoding and a binary as human string."""
|
||||||
|
b = chr(ver) + b
|
||||||
|
return encode(b + checksum(b))
|
||||||
|
|
||||||
|
def decode_version(s):
|
||||||
|
"""Decodes a human base-58 string into its version encoding and binary."""
|
||||||
|
b = decode(s)
|
||||||
|
body, check = b[:-4], b[-4:]
|
||||||
|
assert check == checksum(body), ('Bad checksum for', s)
|
||||||
|
return ord(body[0]), body[1:]
|
||||||
|
|
||||||
|
def version_name(ver):
|
||||||
|
return VERSION_NAME.get(ver) or ('(unknown version %s)' % ver)
|
||||||
|
|
||||||
|
def check_version(version, expected):
|
||||||
|
if version != expected:
|
||||||
|
raise ValueError('Expected version %s but got %s' % (
|
||||||
|
version_name(version), version_name(expected)))
|
||||||
164
bin/python/ripple/util/Sign.py
Normal file
164
bin/python/ripple/util/Sign.py
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import base64, os, random, struct, sys
|
||||||
|
import ed25519
|
||||||
|
import ecdsa
|
||||||
|
from ripple.util import Base58
|
||||||
|
from ripple.ledger import SField
|
||||||
|
|
||||||
|
ED25519_BYTE = chr(0xed)
|
||||||
|
WRAP_COLUMNS = 60
|
||||||
|
|
||||||
|
USAGE = """\
|
||||||
|
Usage:
|
||||||
|
create
|
||||||
|
Create a new master public/secret key pair.
|
||||||
|
|
||||||
|
check <key>
|
||||||
|
Check an existing key for validity.
|
||||||
|
|
||||||
|
sign <sequence> <validator-public> <master-secret>
|
||||||
|
Create a new signed manifest with the given sequence
|
||||||
|
number, validator public key, and master secret key.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def prepend_length_byte(b):
|
||||||
|
assert len(b) <= 192, 'Too long'
|
||||||
|
return chr(len(b)) + b
|
||||||
|
|
||||||
|
def to_int32(i):
|
||||||
|
return struct.pack('>I', i)
|
||||||
|
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
|
||||||
|
def make_seed(urandom=os.urandom):
|
||||||
|
# This is not used.
|
||||||
|
return urandom(16)
|
||||||
|
|
||||||
|
def make_ed25519_keypair(urandom=os.urandom):
|
||||||
|
private_key = urandom(32)
|
||||||
|
return private_key, ed25519.publickey(private_key)
|
||||||
|
|
||||||
|
def make_ecdsa_keypair():
|
||||||
|
# This is not used.
|
||||||
|
private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
|
||||||
|
# Can't be unit tested easily - need a mock for ecdsa.
|
||||||
|
vk = private_key.get_verifying_key()
|
||||||
|
sig = private_key.sign('message')
|
||||||
|
assert vk.verify(sig, 'message')
|
||||||
|
return private_key, vk
|
||||||
|
|
||||||
|
def make_seed_from_passphrase(passphrase):
|
||||||
|
# For convenience, like say testing against rippled we can hash a passphrase
|
||||||
|
# to get the seed. validation_create (Josh may have killed it by now) takes
|
||||||
|
# an optional arg, which can be a base58 encoded seed, or a passphrase.
|
||||||
|
return hashlib.sha512(passphrase).digest()[:16]
|
||||||
|
|
||||||
|
def make_manifest(public_key, validator_public_key, seq):
|
||||||
|
return ''.join([
|
||||||
|
SField.sfSequence,
|
||||||
|
to_int32(seq),
|
||||||
|
SField.sfPublicKey, # Master public key.
|
||||||
|
prepend_length_byte(public_key),
|
||||||
|
SField.sfSigningPubKey, # Ephemeral public key.
|
||||||
|
prepend_length_byte(validator_public_key)])
|
||||||
|
|
||||||
|
def sign_manifest(manifest, private_key, public_key):
|
||||||
|
sig = ed25519.signature('MAN\0' + manifest, private_key, public_key)
|
||||||
|
return manifest + SField.sfSignature + prepend_length_byte(sig)
|
||||||
|
|
||||||
|
def wrap(s, cols=WRAP_COLUMNS):
|
||||||
|
if s:
|
||||||
|
size = max((len(s) + cols - 1) / cols, 1)
|
||||||
|
w = len(s) / size
|
||||||
|
s = '\n'.join(s[i:i + w] for i in range(0, len(s), w))
|
||||||
|
return s
|
||||||
|
|
||||||
|
def create_ed_keys(urandom=os.urandom):
|
||||||
|
private_key, public_key = make_ed25519_keypair(urandom)
|
||||||
|
public_key_human = Base58.encode_version(
|
||||||
|
Base58.VER_NODE_PUBLIC, ED25519_BYTE + public_key)
|
||||||
|
private_key_human = Base58.encode_version(
|
||||||
|
Base58.VER_NODE_PRIVATE, private_key)
|
||||||
|
return public_key_human, private_key_human
|
||||||
|
|
||||||
|
def check_validator_public(v, validator_public_key):
|
||||||
|
Base58.check_version(v, Base58.VER_NODE_PUBLIC)
|
||||||
|
if len(validator_public_key) != 33:
|
||||||
|
raise ValueError('Validator key should be length 33, is %s' %
|
||||||
|
len(validator_public_key))
|
||||||
|
b = ord(validator_public_key[0])
|
||||||
|
if b not in (2, 3):
|
||||||
|
raise ValueError('First validator key byte must be 2 or 3, is %d' % b)
|
||||||
|
|
||||||
|
def check_master_secret(v, private_key):
|
||||||
|
Base58.check_version(v, Base58.VER_NODE_PRIVATE)
|
||||||
|
if len(private_key) != 32:
|
||||||
|
raise ValueError('Length of master secret should be 32, is %s' %
|
||||||
|
len(private_key))
|
||||||
|
|
||||||
|
|
||||||
|
def get_signature(seq, validator_public_key_human, private_key_human):
|
||||||
|
v, validator_public_key = Base58.decode_version(validator_public_key_human)
|
||||||
|
check_validator_public(v, validator_public_key)
|
||||||
|
|
||||||
|
v, private_key = Base58.decode_version(private_key_human)
|
||||||
|
check_master_secret(v, private_key)
|
||||||
|
|
||||||
|
pk = ed25519.publickey(private_key)
|
||||||
|
apk = ED25519_BYTE + pk
|
||||||
|
m = make_manifest(apk, validator_public_key, seq)
|
||||||
|
m1 = sign_manifest(m, private_key, pk)
|
||||||
|
return base64.b64encode(m1)
|
||||||
|
|
||||||
|
|
||||||
|
# Testable versions of functions.
|
||||||
|
def perform_create(urandom=os.urandom, print=print):
|
||||||
|
public, private = create_ed_keys(urandom)
|
||||||
|
print('[validator_keys]', public, '', '[master_secret]', private, sep='\n')
|
||||||
|
|
||||||
|
def perform_check(s, print=print):
|
||||||
|
version, b = Base58.decode_version(s)
|
||||||
|
print('version = ' + Base58.version_name(version))
|
||||||
|
print('decoded length = ' + str(len(b)))
|
||||||
|
assert Base58.encode_version(version, b) == s
|
||||||
|
|
||||||
|
def perform_sign(
|
||||||
|
seq, validator_public_key_human, private_key_human, print=print):
|
||||||
|
print('[validation_manifest]')
|
||||||
|
print(wrap(get_signature(
|
||||||
|
int(seq), validator_public_key_human, private_key_human)))
|
||||||
|
|
||||||
|
# Externally visible versions of functions.
|
||||||
|
def create():
|
||||||
|
perform_create()
|
||||||
|
|
||||||
|
def check(s):
|
||||||
|
perform_check(s)
|
||||||
|
|
||||||
|
def sign(seq, validator_public_key_human, private_key_human):
|
||||||
|
perform_sign(seq, validator_public_key_human, private_key_human)
|
||||||
|
|
||||||
|
|
||||||
|
def usage(*errors):
|
||||||
|
if errors:
|
||||||
|
print(*errors)
|
||||||
|
print(USAGE)
|
||||||
|
return not errors
|
||||||
|
|
||||||
|
_COMMANDS = dict((f.__name__, f) for f in (create, check, sign))
|
||||||
|
|
||||||
|
def run_command(args):
|
||||||
|
if not args:
|
||||||
|
return usage()
|
||||||
|
name = args[0]
|
||||||
|
command = _COMMANDS.get(name)
|
||||||
|
if not command:
|
||||||
|
return usage('No such command:', command)
|
||||||
|
try:
|
||||||
|
command(*args[1:])
|
||||||
|
except TypeError:
|
||||||
|
return usage('Wrong number of arguments for:', command)
|
||||||
|
return True
|
||||||
47
bin/python/ripple/util/test_Base58.py
Normal file
47
bin/python/ripple/util/test_Base58.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
from ripple.util import Base58
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
BINARY = 'nN9kfUnKTf7PpgLG'
|
||||||
|
|
||||||
|
class test_Base58(TestCase):
|
||||||
|
def run_test(self, before, after):
|
||||||
|
self.assertEquals(Base58.decode(before), after)
|
||||||
|
self.assertEquals(Base58.encode(after), before)
|
||||||
|
|
||||||
|
def test_trivial(self):
|
||||||
|
self.run_test('', '')
|
||||||
|
|
||||||
|
def test_zeroes(self):
|
||||||
|
for before, after in (('', ''), ('abc', 'I\x8b')):
|
||||||
|
for i in range(1, 257):
|
||||||
|
self.run_test('r' * i + before, '\0' * i + after)
|
||||||
|
|
||||||
|
def test_single_digits(self):
|
||||||
|
for i, c in enumerate(Base58.ALPHABET):
|
||||||
|
self.run_test(c, chr(i))
|
||||||
|
|
||||||
|
def test_various(self):
|
||||||
|
# Test three random numbers.
|
||||||
|
self.run_test('88Mw', '\x88L\xed')
|
||||||
|
self.run_test(
|
||||||
|
'nN9kfUnKTf7PpgLG', '\x03\xdc\x9co\xdea\xefn\xd3\xb8\xe2\xc1')
|
||||||
|
self.run_test(
|
||||||
|
'zzWWb4C5p6kNrVa4fEBoZpZKd3XQLXch7QJbLCuLdoS1CWr8qdAZHEmwMiJy8Hwp',
|
||||||
|
'xN\x82\xfcQ\x1f\xb3~\xdf\xc7\xb37#\xc6~A\xe9\xf6-\x1f\xcb"\xfab'
|
||||||
|
'(\'\xccv\x9e\x85\xc3\xd1\x19\x941{\x8et\xfbS}\x86.k\x07\xb5\xb3')
|
||||||
|
|
||||||
|
def test_check(self):
|
||||||
|
self.assertEquals(Base58.checksum(BINARY), '\xaa\xaar\x9d')
|
||||||
|
|
||||||
|
def test_encode(self):
|
||||||
|
self.assertEquals(
|
||||||
|
Base58.encode_version(Base58.VER_ACCOUNT_PUBLIC, BINARY),
|
||||||
|
'sB49XwJgmdEZDo8LmYwki7FYkiaN7')
|
||||||
|
|
||||||
|
def test_decode(self):
|
||||||
|
ver, b = Base58.decode_version('sB49XwJgmdEZDo8LmYwki7FYkiaN7')
|
||||||
|
self.assertEquals(ver, Base58.VER_ACCOUNT_PUBLIC)
|
||||||
|
self.assertEquals(b, BINARY)
|
||||||
127
bin/python/ripple/util/test_Sign.py
Normal file
127
bin/python/ripple/util/test_Sign.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
from ripple.util import Sign
|
||||||
|
from ripple.util import Base58
|
||||||
|
from ripple.ledger import SField
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
BINARY = 'nN9kfUnKTf7PpgLG'
|
||||||
|
|
||||||
|
class test_Sign(TestCase):
|
||||||
|
SEQUENCE = 23
|
||||||
|
SIGNATURE = (
|
||||||
|
'JAAAABdxIe2DIKUZd9jDjKikknxnDfWCHkSXYZReFenvsmoVCdIw6nMhAnZ2dnZ2'
|
||||||
|
'dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dkDOjlWtQSvRTjuwe+4iNusg0sJM'
|
||||||
|
'zqkBJwDz30b2SkxZ7Fte/Vx4htM/kkfUfJCaxmxE5N4dHSKuiO9iDHsktqIA')
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.results = []
|
||||||
|
|
||||||
|
def print(self, *args, **kwds):
|
||||||
|
self.results.append([list(args), kwds])
|
||||||
|
|
||||||
|
def test_field_code(self):
|
||||||
|
self.assertEquals(SField.field_code(SField.STI_UINT32, 4), '$')
|
||||||
|
self.assertEquals(SField.field_code(SField.STI_VL, 1), 'q')
|
||||||
|
self.assertEquals(SField.field_code(SField.STI_VL, 3), 's')
|
||||||
|
self.assertEquals(SField.field_code(SField.STI_VL, 6), 'v')
|
||||||
|
|
||||||
|
def test_strvl(self):
|
||||||
|
self.assertEquals(Sign.prepend_length_byte(BINARY),
|
||||||
|
'\x10nN9kfUnKTf7PpgLG')
|
||||||
|
|
||||||
|
def urandom(self, bytes):
|
||||||
|
return '\5' * bytes
|
||||||
|
|
||||||
|
def test_make_seed(self):
|
||||||
|
self.assertEquals(Sign.make_seed(self.urandom),
|
||||||
|
'\5\5\5\5\5\5\5\5\5\5\5\5\5\5\5\5')
|
||||||
|
|
||||||
|
def test_make_ed(self):
|
||||||
|
private, public = Sign.make_ed25519_keypair(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_make_manifest(self):
|
||||||
|
_, pk = Sign.make_ed25519_keypair(self.urandom)
|
||||||
|
m = Sign.make_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 = Sign.make_ed25519_keypair(self.urandom)
|
||||||
|
s = Sign.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: Sign.wrap(s, 5)
|
||||||
|
self.assertEquals(wrap(''), '')
|
||||||
|
self.assertEquals(wrap('12345'), '12345')
|
||||||
|
self.assertEquals(wrap('123456'), '123\n456')
|
||||||
|
self.assertEquals(wrap('12345678'), '1234\n5678')
|
||||||
|
self.assertEquals(wrap('1234567890'), '12345\n67890')
|
||||||
|
self.assertEquals(wrap('12345678901'), '123\n456\n789\n01')
|
||||||
|
|
||||||
|
def test_create_ed_keys(self):
|
||||||
|
pkh, skh = Sign.create_ed_keys(self.urandom)
|
||||||
|
self.assertEquals(
|
||||||
|
pkh, 'nHUUaKHpxyRP4TZZ79tTpXuTpoM8pRNs5crZpGVA5jdrjib5easY')
|
||||||
|
self.assertEquals(
|
||||||
|
skh, 'pnEp13Zu7xTeKQVQ2RZVaUraE9GXKqFtnXQVUFKXbTE6wsP4wne')
|
||||||
|
|
||||||
|
def get_test_keypair(self):
|
||||||
|
public = (Base58.VER_NODE_PUBLIC, '\x02' + (32 * 'v'))
|
||||||
|
private = (Base58.VER_NODE_PRIVATE, 32 * 'k')
|
||||||
|
|
||||||
|
Sign.check_validator_public(*public)
|
||||||
|
Sign.check_master_secret(*private)
|
||||||
|
|
||||||
|
return (Base58.encode_version(*public), Base58.encode_version(*private))
|
||||||
|
|
||||||
|
def test_get_signature(self):
|
||||||
|
signature = Sign.get_signature(self.SEQUENCE, *self.get_test_keypair())
|
||||||
|
self.assertEquals(
|
||||||
|
signature,
|
||||||
|
'JAAAABdxIe2DIKUZd9jDjKikknxnDfWCHkSXYZReFenvsmoVCdIw6nMhAnZ2dnZ2'
|
||||||
|
'dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dkDOjlWtQSvRTjuwe+4iNusg0sJM'
|
||||||
|
'zqkBJwDz30b2SkxZ7Fte/Vx4htM/kkfUfJCaxmxE5N4dHSKuiO9iDHsktqIA')
|
||||||
|
|
||||||
|
def test_check(self):
|
||||||
|
public = Base58.encode_version(Base58.VER_NODE_PRIVATE, 32 * 'k')
|
||||||
|
Sign.perform_check(public, self.print)
|
||||||
|
self.assertEquals(self.results,
|
||||||
|
[[['version = VER_NODE_PRIVATE'], {}],
|
||||||
|
[['decoded length = 32'], {}]])
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
Sign.perform_create(self.urandom, self.print)
|
||||||
|
self.assertEquals(
|
||||||
|
self.results,
|
||||||
|
[[['[validator_keys]',
|
||||||
|
'nHUUaKHpxyRP4TZZ79tTpXuTpoM8pRNs5crZpGVA5jdrjib5easY',
|
||||||
|
'',
|
||||||
|
'[master_secret]',
|
||||||
|
'pnEp13Zu7xTeKQVQ2RZVaUraE9GXKqFtnXQVUFKXbTE6wsP4wne'],
|
||||||
|
{'sep': '\n'}]])
|
||||||
|
|
||||||
|
def test_sign(self):
|
||||||
|
public, private = self.get_test_keypair()
|
||||||
|
Sign.perform_sign(self.SEQUENCE, public, private, print=self.print)
|
||||||
|
self.assertEquals(
|
||||||
|
self.results,
|
||||||
|
[[['[validation_manifest]'], {}],
|
||||||
|
[['JAAAABdxIe2DIKUZd9jDjKikknxnDfWCHkSXYZReFenvsmo\n'
|
||||||
|
'VCdIw6nMhAnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dn\n'
|
||||||
|
'Z2dnZ2dkDOjlWtQSvRTjuwe+4iNusg0sJMzqkBJwDz30b2S\n'
|
||||||
|
'kxZ7Fte/Vx4htM/kkfUfJCaxmxE5N4dHSKuiO9iDHsktqIA'],
|
||||||
|
{}]])
|
||||||
Reference in New Issue
Block a user