mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 19:45:53 +00:00
Sign manifest with ephemeral and master keys (RIPD-1083)
This commit is contained in:
committed by
Edward Hennis
parent
7d46d153c6
commit
b55edfa8f0
@@ -5,11 +5,12 @@ from __future__ import print_function
|
||||
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:
|
||||
@@ -22,12 +23,9 @@ Usage:
|
||||
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):
|
||||
@@ -44,17 +42,17 @@ 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
|
||||
@@ -62,18 +60,58 @@ def make_seed_from_passphrase(passphrase):
|
||||
# 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)])
|
||||
SField.sfPublicKey,
|
||||
prepend_length_byte(master_pk),
|
||||
SField.sfSigningPubKey,
|
||||
prepend_length_byte(validation_pk)])
|
||||
|
||||
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 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:
|
||||
@@ -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
|
||||
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(private_key_human):
|
||||
v, private_key = Base58.decode_version(private_key_human)
|
||||
check_master_secret(v, private_key)
|
||||
def create_ed_public_key(sk_human):
|
||||
v, sk = Base58.decode_version(sk_human)
|
||||
check_secret_key(v, sk)
|
||||
|
||||
public_key = ed25519.publickey(private_key)
|
||||
public_key_human = Base58.encode_version(
|
||||
Base58.VER_NODE_PUBLIC, ED25519_BYTE + public_key)
|
||||
return public_key_human
|
||||
pk = ed25519.publickey(sk)
|
||||
pk_human = Base58.encode_version(
|
||||
Base58.VER_NODE_PUBLIC, ED25519_BYTE + pk)
|
||||
return pk_human
|
||||
|
||||
def check_validator_public(v, validator_public_key):
|
||||
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)
|
||||
@@ -157,32 +187,29 @@ 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:
|
||||
@@ -190,7 +217,7 @@ def usage(*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:
|
||||
|
||||
Reference in New Issue
Block a user