Skip to content

Commit

Permalink
Sign manifest with ephemeral and master keys (RIPD-1083)
Browse files Browse the repository at this point in the history
  • Loading branch information
wilsonianb authored and ximinez committed Oct 17, 2016
1 parent 7d46d15 commit b55edfa
Show file tree
Hide file tree
Showing 15 changed files with 266 additions and 186 deletions.
9 changes: 8 additions & 1 deletion bin/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,11 @@ To run the Python unit tests, execute:

python -m unittest discover

from this directory.
from this directory.

To run Python unit tests from a particular file (such as
`ripple/util/test_Sign.py`), execute:

python -m unittest ripple.util.test_Sign

Add `-v` to run tests in verbose mode.
1 change: 1 addition & 0 deletions bin/python/ripple/ledger/SField.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ def field_code(sti, name):
sfPublicKey = field_code(STI_VL, 1)
sfSigningPubKey = field_code(STI_VL, 3)
sfSignature = field_code(STI_VL, 6)
sfMasterSignature = field_code(STI_VL, 18)
199 changes: 113 additions & 86 deletions bin/python/ripple/util/Sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import base64, os, random, struct, sys
import ed25519
import ecdsa
import hashlib
from ripple.util import Base58
from ripple.ledger import SField

ED25519_BYTE = chr(0xed)
WRAP_COLUMNS = 60
WRAP_COLUMNS = 72

USAGE = """\
Usage:
Expand All @@ -22,12 +23,9 @@
check <key>
Check an existing key for validity.
sign <sequence> <validator-public> <master-secret>
sign <sequence> <validation-public-key> <validation-private-key> <master-secret>
Create a new signed manifest with the given sequence
number, validator public key, and master secret key.
verify <sequence> <validator-public> <signature> <master-public>
Verify hex-encoded manifest signature with master public key.
number and keys.
"""

def prepend_length_byte(b):
Expand All @@ -44,36 +42,76 @@ def make_seed(urandom=os.urandom):
return urandom(16)

def make_ed25519_keypair(urandom=os.urandom):
private_key = urandom(32)
return private_key, ed25519.publickey(private_key)
sk = urandom(32)
return sk, ed25519.publickey(sk)

def make_ecdsa_keypair():
def make_ecdsa_keypair(urandom=None):
# This is not used.
private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
sk = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1, entropy=urandom)
# Can't be unit tested easily - need a mock for ecdsa.
vk = private_key.get_verifying_key()
sig = private_key.sign('message')
vk = sk.get_verifying_key()
sig = sk.sign('message')
assert vk.verify(sig, 'message')
return private_key, vk
return sk, 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):
def make_manifest(master_pk, validation_pk, seq):
"""create a manifest
Parameters
----------
master_pk : string
validator's master public key (binary, _not_ BASE58 encoded)
validation_pk : string
validator's validation public key (binary, _not_ BASE58 encoded)
seq : int
manifest sequence number
Returns
----------
string
String with fields for seq, master_pk, validation_pk
"""
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)
SField.sfPublicKey,
prepend_length_byte(master_pk),
SField.sfSigningPubKey,
prepend_length_byte(validation_pk)])

def sign_manifest(manifest, validation_sk, master_sk, master_pk):
"""sign a validator manifest
Parameters
----------
manifest : string
manifest to sign
validation_sk : string
validator's validation secret key (binary, _not_ BASE58 encoded)
This is one of the keys that will sign the manifest.
master_sk : string
validator's master secret key (binary, _not_ BASE58 encoded)
This is one of the keys that will sign the manifest.
master_pk : string
validator's master public key (binary, _not_ BASE58 encoded)
Returns
----------
string
manifest signed by both the validation and master keys
"""
man_hash = hashlib.sha512('MAN\0' + manifest).digest()[:32]
validation_sig = validation_sk.sign_digest_deterministic(
man_hash, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der_canonize)
master_sig = ed25519.signature('MAN\0' + manifest, master_sk, master_pk)
return manifest + SField.sfSignature + prepend_length_byte(validation_sig) + \
SField.sfMasterSignature + prepend_length_byte(master_sig)

def wrap(s, cols=WRAP_COLUMNS):
if s:
Expand All @@ -83,72 +121,64 @@ def wrap(s, cols=WRAP_COLUMNS):
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 create_ed_public_key(private_key_human):
v, private_key = Base58.decode_version(private_key_human)
check_master_secret(v, private_key)

public_key = ed25519.publickey(private_key)
public_key_human = Base58.encode_version(
Base58.VER_NODE_PUBLIC, ED25519_BYTE + public_key)
return public_key_human

def check_validator_public(v, validator_public_key):
sk, pk = make_ed25519_keypair(urandom)
pk_human = Base58.encode_version(
Base58.VER_NODE_PUBLIC, ED25519_BYTE + pk)
sk_human = Base58.encode_version(
Base58.VER_NODE_PRIVATE, sk)
return pk_human, sk_human

def create_ed_public_key(sk_human):
v, sk = Base58.decode_version(sk_human)
check_secret_key(v, sk)

pk = ed25519.publickey(sk)
pk_human = Base58.encode_version(
Base58.VER_NODE_PUBLIC, ED25519_BYTE + pk)
return pk_human

def check_validation_public_key(v, pk):
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 len(pk) != 33:
raise ValueError('Validation public key should be length 33, is %s' %
len(pk))
b = ord(pk[0])
if b not in (2, 3):
raise ValueError('First validator key byte must be 2 or 3, is %d' % b)
raise ValueError('First validation public key byte must be 2 or 3, is %d' % b)

def check_master_secret(v, private_key):
def check_secret_key(v, sk):
Base58.check_version(v, Base58.VER_NODE_PRIVATE)
if len(private_key) != 32:
if len(sk) != 32:
raise ValueError('Length of master secret should be 32, is %s' %
len(private_key))
len(sk))

def get_signature(seq, validation_pk_human, validation_sk_human, master_sk_human):
v, validation_pk = Base58.decode_version(validation_pk_human)
check_validation_public_key(v, validation_pk)

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, validation_sk_str = Base58.decode_version(validation_sk_human)
check_secret_key(v, validation_sk_str)
validation_sk = ecdsa.SigningKey.from_string(validation_sk_str, curve=ecdsa.SECP256k1)

v, private_key = Base58.decode_version(private_key_human)
check_master_secret(v, private_key)
v, master_sk = Base58.decode_version(master_sk_human)
check_secret_key(v, master_sk)

pk = ed25519.publickey(private_key)
pk = ed25519.publickey(master_sk)
apk = ED25519_BYTE + pk
m = make_manifest(apk, validator_public_key, seq)
m1 = sign_manifest(m, private_key, pk)
m = make_manifest(apk, validation_pk, seq)
m1 = sign_manifest(m, validation_sk, master_sk, pk)
return base64.b64encode(m1)

def verify_signature(seq, validator_public_key_human, public_key_human, signature):
v, validator_public_key = Base58.decode_version(validator_public_key_human)
check_validator_public(v, validator_public_key)

v, public_key = Base58.decode_version(public_key_human)

m = make_manifest(public_key, validator_public_key, seq)
public_key = public_key[1:] # Remove ED25519_BYTE
sig = signature.decode('hex')
ed25519.checkvalid(sig, 'MAN\0' + m, public_key)

# 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')
pk, sk = create_ed_keys(urandom)
print('[validator_keys]', pk, '', '[master_secret]', sk, sep='\n')

def perform_create_public(private_key_human, print=print):
public_key_human = create_ed_public_key(private_key_human)
def perform_create_public(sk_human, print=print):
pk_human = create_ed_public_key(sk_human)
print(
'[validator_keys]',public_key_human, '',
'[master_secret]', private_key_human, sep='\n')
'[validator_keys]',pk_human, '',
'[master_secret]', sk_human, sep='\n')

def perform_check(s, print=print):
version, b = Base58.decode_version(s)
Expand All @@ -157,40 +187,37 @@ def perform_check(s, print=print):
assert Base58.encode_version(version, b) == s

def perform_sign(
seq, validator_public_key_human, private_key_human, print=print):
seq, validation_pk_human, validation_sk_human, master_sk_human, print=print):
print('[validation_manifest]')
print(wrap(get_signature(
int(seq), validator_public_key_human, private_key_human)))
int(seq), validation_pk_human, validation_sk_human, master_sk_human)))

def perform_verify(
seq, validator_public_key_human, public_key_human, signature, print=print):
seq, validation_pk_human, master_pk_human, signature, print=print):
verify_signature(
int(seq), validator_public_key_human, public_key_human, signature)
print('Signature valid for', public_key_human)
int(seq), validation_pk_human, master_pk_human, signature)
print('Signature valid for', master_pk_human)

# Externally visible versions of functions.
def create(private_key_human=None):
if private_key_human:
perform_create_public(private_key_human)
def create(sk_human=None):
if sk_human:
perform_create_public(sk_human)
else:
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 verify(seq, validator_public_key_human, public_key_human, signature):
perform_verify(seq, validator_public_key_human, public_key_human, signature)
def sign(seq, validation_pk_human, validation_sk_human, master_sk_human):
perform_sign(seq, validation_pk_human, validation_sk_human, master_sk_human)

def usage(*errors):
if errors:
print(*errors)
print(USAGE)
return not errors

_COMMANDS = dict((f.__name__, f) for f in (create, check, sign, verify))
_COMMANDS = dict((f.__name__, f) for f in (create, check, sign))

def run_command(args):
if not args:
Expand Down
Loading

0 comments on commit b55edfa

Please sign in to comment.