diff --git a/bin/python/README.md b/bin/python/README.md index 9e0e6c1e86..7b49c4d657 100644 --- a/bin/python/README.md +++ b/bin/python/README.md @@ -5,4 +5,11 @@ To run the Python unit tests, execute: python -m unittest discover -from this directory. \ No newline at end of file +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. diff --git a/bin/python/ripple/ledger/SField.py b/bin/python/ripple/ledger/SField.py index 1dd0fd20ea..513e36a661 100644 --- a/bin/python/ripple/ledger/SField.py +++ b/bin/python/ripple/ledger/SField.py @@ -49,3 +49,4 @@ sfSequence = field_code(STI_UINT32, 4) sfPublicKey = field_code(STI_VL, 1) sfSigningPubKey = field_code(STI_VL, 3) sfSignature = field_code(STI_VL, 6) +sfMasterSignature = field_code(STI_VL, 18) diff --git a/bin/python/ripple/util/Sign.py b/bin/python/ripple/util/Sign.py index 75529f6f7d..e0eda78f37 100644 --- a/bin/python/ripple/util/Sign.py +++ b/bin/python/ripple/util/Sign.py @@ -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 Check an existing key for validity. - sign + sign Create a new signed manifest with the given sequence - number, validator public key, and master secret key. - - verify - 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: diff --git a/bin/python/ripple/util/test_Sign.py b/bin/python/ripple/util/test_Sign.py index 0f67912cd0..06ab00df76 100644 --- a/bin/python/ripple/util/test_Sign.py +++ b/bin/python/ripple/util/test_Sign.py @@ -10,14 +10,7 @@ BINARY = 'nN9kfUnKTf7PpgLG' class test_Sign(TestCase): SEQUENCE = 23 - SIGNATURE = ( - 'JAAAABdxIe2DIKUZd9jDjKikknxnDfWCHkSXYZReFenvsmoVCdIw6nMhAnZ2dnZ2' - 'dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dkDOjlWtQSvRTjuwe+4iNusg0sJM' - 'zqkBJwDz30b2SkxZ7Fte/Vx4htM/kkfUfJCaxmxE5N4dHSKuiO9iDHsktqIA') VALIDATOR_KEY_HUMAN = 'n9JijuoCv8ubEy5ag3LiX3hyq27GaLJsitZPbQ6APkwx2MkUXq8E' - SIGNATURE_HEX = ( - '0a1546caa29c887f9fcb5e6143ea101b31fb5895a5cdfa24939301c66ff51794' - 'a0b729e0ebbf576f2cc7cdb9f68c2366324a53b8e1ecf16f3c17bebbdb8d7102') def setUp(self): self.results = [] @@ -59,13 +52,17 @@ class test_Sign(TestCase): '\xef*\x97\x16n<\xa6\xf2\xe4\xfb\xfc\xcd\x80P[\xf1s\x06verify') def test_sign_manifest(self): + ssk, spk = Sign.make_ecdsa_keypair(self.urandom) sk, pk = Sign.make_ed25519_keypair(self.urandom) - s = Sign.sign_manifest('manifest', sk, pk) + s = Sign.sign_manifest('manifest', ssk, 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') + s, 'manifestvF0D\x02 \x04\x85\x95p\x0f\xb8\x17\x7f\xdf\xdd\x04' + '\xaa+\x16q1W\xf6\xfd\xe8X\xb12l\xd5\xc3\xf1\xd6\x05\x1b\x1c\x9a' + '\x02 \x18\\.(o\xa8 \xeb\x87\xfa&~\xbd\xe6,\xfb\xa61\xd1\xcd\xcd' + '\xc8\r\x16[\x81\x9a\x19\xda\x93i\xcdp\x12@\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) @@ -93,23 +90,21 @@ class test_Sign(TestCase): 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) + Sign.check_validation_public_key(*public) + Sign.check_secret_key(*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()) + pk, sk = self.get_test_keypair() + signature = Sign.get_signature(self.SEQUENCE, pk, sk, sk) self.assertEquals( signature, 'JAAAABdxIe2DIKUZd9jDjKikknxnDfWCHkSXYZReFenvsmoVCdIw6nMhAnZ2dnZ2' - 'dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dkDOjlWtQSvRTjuwe+4iNusg0sJM' - 'zqkBJwDz30b2SkxZ7Fte/Vx4htM/kkfUfJCaxmxE5N4dHSKuiO9iDHsktqIA') - - def test_verify_signature(self): - Sign.verify_signature(self.SEQUENCE, self.VALIDATOR_KEY_HUMAN, - 'nHUUaKHpxyRP4TZZ79tTpXuTpoM8pRNs5crZpGVA5jdrjib5easY', - self.SIGNATURE_HEX) + 'dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dkYwRAIgXyobHA8sDQxmDJNLE6HI' + 'aARlzvcd79/wT068e113gUkCIHkI540JQT2LHwAD7/y3wFE5X3lEXMfgZRkpLZTx' + 'kpticBJAzo5VrUEr0U47sHvuIjbrINLCTM6pAScA899G9kpMWexbXv1ceIbTP5JH' + '1HyQmsZsROTeHR0irojvYgx7JLaiAA==') def test_check(self): public = Base58.encode_version(Base58.VER_NODE_PRIVATE, 32 * 'k') @@ -143,17 +138,12 @@ class test_Sign(TestCase): def test_sign(self): public, private = self.get_test_keypair() - Sign.perform_sign(self.SEQUENCE, public, private, print=self.print) + Sign.perform_sign(self.SEQUENCE, public, private, private, print=self.print) self.assertEquals( self.results, [[['[validation_manifest]'], {}], - [['JAAAABdxIe2DIKUZd9jDjKikknxnDfWCHkSXYZReFenvsmo\n' - 'VCdIw6nMhAnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dn\n' - 'Z2dnZ2dkDOjlWtQSvRTjuwe+4iNusg0sJMzqkBJwDz30b2S\n' - 'kxZ7Fte/Vx4htM/kkfUfJCaxmxE5N4dHSKuiO9iDHsktqIA'], + [['JAAAABdxIe2DIKUZd9jDjKikknxnDfWCHkSXYZReFenvsmoVCdIw6nMhAnZ2dnZ2dnZ2dnZ2\n' + 'dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dkYwRAIgXyobHA8sDQxmDJNLE6HIaARlzvcd79/wT068\n' + 'e113gUkCIHkI540JQT2LHwAD7/y3wFE5X3lEXMfgZRkpLZTxkpticBJAzo5VrUEr0U47sHvu\n' + 'IjbrINLCTM6pAScA899G9kpMWexbXv1ceIbTP5JH1HyQmsZsROTeHR0irojvYgx7JLaiAA=='], {}]]) - - def test_verify(self): - Sign.perform_verify(self.SEQUENCE, self.VALIDATOR_KEY_HUMAN, - 'nHUUaKHpxyRP4TZZ79tTpXuTpoM8pRNs5crZpGVA5jdrjib5easY', - self.SIGNATURE_HEX, print=self.print) diff --git a/doc/manifest-tool-guide.md b/doc/manifest-tool-guide.md index 4998c5d68a..a27be5df66 100644 --- a/doc/manifest-tool-guide.md +++ b/doc/manifest-tool-guide.md @@ -22,10 +22,10 @@ master key pair: Sample output: ``` [validator_keys] - nHUSSzGw4A9zEmFtK2Q2NcWDH9xmGdXMHc1MsVej3QkLTgvDNeBr + nHDwNP6jbJgVKj5zSEjMn8SJhTnPV54Fx5sSiNpwqLbb42nmh3Ln [master_secret] - pnxayCakmZRQE2qhEVRbFjiWCunReSbN1z64vPL36qwyLgogyYc + paamfhAn5m1NM4UUu5mmvVaHQy8Fb65bkpbaNrvKwX3YMKdjzi2 ``` The first value is the master public key. Add the public key to the config @@ -54,10 +54,11 @@ Sample output: Securely connecting to 127.0.0.1:5005 { "result" : { - "status" : "success", - "validation_key" : "TOO EDNA SHUN FEUD STAB JOAN BIAS FLEA WISE BOHR LOSS WEEK", - "validation_public_key" : "n9JzKV3ZrcZ3DW5pwjakj4hpijJ9oMiyrPDGJc3mpsndL6Gf3zwd", - "validation_seed" : "sahzkAajS2dyhjXg2yovjdZhXmjsx" + "status" : "success", + "validation_key" : "MIN HEAT NUN FAWN HIP KAHN BORG PHI BALK ANN TWIG RACY", + "validation_private_key" : "pn6kTqE4WyeRQzZrRp5FnJ5J2vLdSCB5KD3DEyfVw6C9woDBfED", + "validation_public_key" : "n9LtZ9haqYMbzJ92cDd3pu3Lko6uEznrXuYea3ehuhVcwDHF5coX", + "validation_seed" : "sh8bLqqkGBknGcsgRTrFMxcciwytm" } } ``` @@ -68,36 +69,37 @@ number as a comment as well (sequence numbers are be explained below): ``` [validation_seed] - sahzkAajS2dyhjXg2yovjdZhXmjsx - # validation_public_key: n9JzKV3ZrcZ3DW5pwjakj4hpijJ9oMiyrPDGJc3mpsndL6Gf3zwd + sh8bLqqkGBknGcsgRTrFMxcciwytm + # validation_public_key: n9LtZ9haqYMbzJ92cDd3pu3Lko6uEznrXuYea3ehuhVcwDHF5coX # sequence number: 1 ``` A manifest is a signed message used to inform other servers of this validator's ephemeral public key. A manifest contains a sequence number, the new ephemeral -public key, and it is signed with the master secret key. The sequence number -should be higher than the previous sequence number (if it is not, the manifest -will be ignored). Usually the previous sequence number will be incremented by -one. Use the `manifest` script to create a manifest. It has the form: +public key, and it is signed with both the ephemeral and master secret keys. +The sequence number should be higher than the previous sequence number (if it +is not, the manifest will be ignored). Usually the previous sequence number +will be incremented by one. Use the `manifest` script to create a manifest. +It has the form: ``` - $ bin/manifest sign sequence_number validation_public_key master_secret + $ bin/manifest sign sequence validation_public_key validation_private_key master_secret ``` For example: ``` - $ bin/manifest sign 1 n9JzKV3Z...L6Gf3zwd pnxayCak...yLgogyYc + $ bin/manifest sign 1 n9LtZ9ha...wDHF5coX pn6kTqE4...9woDBfED paamfhAn...YMKdjzi2 ``` Sample output: ``` [validation_manifest] - JAAAAAFxIe2PEzNhe996gykB1PJQNoDxvr/Y0XhDELw8d/i - Fcgz3A3MhAjqhKsgZTmK/3BPEI+kzjV1p9ip7pl/AtF7CKd - NSfAH9dkCxezV6apS4FLYzAcQilONx315HvebwAB/pLPaM4 - 2sWCEppSuLNKN/JJjTABOo9tmAiNnnstF83yvecKMJzniwN + JAAAAAFxIe3t8rIb4Ba8JHI97CbwpxmTq0LhN/7ZAbsNaSwrbHaypHMhAzTuu07YGOvVvB3+ + aS0jhP+q0TVgTjGJKhx+yTY1Da3ddkYwRAIgDkmIt3dPNsfeCH3ApMZgpwqG4JwtIlKEymqK + S7v+VqkCIFQXg20ZMpXXT86vmLdlmPspgeUN1scWsuFoPYUUJywycBJAl93+/bZbfZ4quTeM + 5y80/OSIcVoWPcHajwrAl68eiAW4MVFeJXvShXNfnT+XsxMjDh0VpOkhvyp971i1MgjBAA== ``` Copy this to the config for this validator. Don't forget to update the comment @@ -107,4 +109,4 @@ noting the sequence number. If a master key is compromised, the key may be revoked permanently. To revoke a master key, sign a manifest with the highest possible sequence number: -`4,294,967,295` +`4294967295` diff --git a/src/ripple/overlay/impl/Manifest.cpp b/src/ripple/overlay/impl/Manifest.cpp index e01b94647e..c9cb27c381 100644 --- a/src/ripple/overlay/impl/Manifest.cpp +++ b/src/ripple/overlay/impl/Manifest.cpp @@ -42,7 +42,8 @@ make_Manifest (std::string s) auto const opt_spk = get(st, sfSigningPubKey); auto const opt_seq = get (st, sfSequence); auto const opt_sig = get (st, sfSignature); - if (!opt_pk || !opt_spk || !opt_seq || !opt_sig) + auto const opt_msig = get (st, sfMasterSignature); + if (!opt_pk || !opt_spk || !opt_seq || !opt_sig || !opt_msig) { return boost::none; } @@ -99,7 +100,11 @@ bool Manifest::verify () const STObject st (sfGeneric); SerialIter sit (serialized.data (), serialized.size ()); st.set (sit); - return ripple::verify (st, HashPrefix::manifest, masterKey, true); + if (! ripple::verify (st, HashPrefix::manifest, signingKey, true)) + return false; + + return ripple::verify ( + st, HashPrefix::manifest, masterKey, true, sfMasterSignature); } uint256 Manifest::hash () const @@ -127,6 +132,14 @@ Blob Manifest::getSignature () const return st.getFieldVL (sfSignature); } +Blob Manifest::getMasterSignature () const +{ + STObject st (sfGeneric); + SerialIter sit (serialized.data (), serialized.size ()); + st.set (sit); + return st.getFieldVL (sfMasterSignature); +} + bool ManifestCache::loadValidatorKeys( Section const& keys, @@ -415,7 +428,9 @@ void ManifestCache::load ( { if (!mo->verify()) { - Throw ("Unverifiable manifest in db"); + JLOG(journal.warn()) + << "Unverifiable manifest in db"; + continue; } if (trusted(mo->masterKey) || unl.trusted(mo->masterKey)) @@ -444,13 +459,15 @@ void ManifestCache::save (DatabaseCon& dbCon) const *db << "DELETE FROM ValidatorManifests"; static const char* const sql = "INSERT INTO ValidatorManifests (RawData) VALUES (:rawData);"; - // soci does not support bulk insertion of blob data - soci::blob rawData(*db); for (auto const& v : map_) { if (!v.second.m) continue; + // soci does not support bulk insertion of blob data + // Do not reuse blob because manifest ecdsa signatures vary in length + // but blob write length is expected to be >= the last write + soci::blob rawData(*db); convert (v.second.m->serialized, rawData); *db << sql, soci::use (rawData); diff --git a/src/ripple/overlay/impl/Manifest.h b/src/ripple/overlay/impl/Manifest.h index a0b48eb714..fd9ca966bf 100644 --- a/src/ripple/overlay/impl/Manifest.h +++ b/src/ripple/overlay/impl/Manifest.h @@ -99,6 +99,9 @@ struct Manifest uint256 hash () const; bool revoked () const; Blob getSignature () const; + + /// Returns manifest master key signature + Blob getMasterSignature () const; }; boost::optional make_Manifest(std::string s); diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index cde457a0f8..3539dceed0 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -441,6 +441,7 @@ JSS ( validated_ledger ); // out: NetworkOPs JSS ( validated_ledgers ); // out: NetworkOPs JSS ( validation_key ); // out: ValidationCreate, ValidationSeed JSS ( validation_manifest ); // out: NetworkOPs +JSS ( validation_private_key ); // out: ValidationCreate JSS ( validation_public_key ); // out: ValidationCreate, ValidationSeed JSS ( validation_quorum ); // out: NetworkOPs JSS ( validation_seed ); // out: ValidationCreate, ValidationSeed diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index 802d7cd674..ecb86f8e55 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -447,6 +447,7 @@ extern SF_Blob const sfCreateCode; extern SF_Blob const sfMemoType; extern SF_Blob const sfMemoData; extern SF_Blob const sfMemoFormat; +extern SF_Blob const sfMasterSignature; // variable length (uncommon) extern SF_Blob const sfProof; diff --git a/src/ripple/protocol/Sign.h b/src/ripple/protocol/Sign.h index 4fc3e4e288..1872ba5939 100644 --- a/src/ripple/protocol/Sign.h +++ b/src/ripple/protocol/Sign.h @@ -28,23 +28,34 @@ namespace ripple { -/** Sign a STObject using any secret key. - The signature is placed in sfSignature. If - a signature already exists, it is overwritten. +/** Sign an STObject + + @param st Object to sign + @param prefix Prefix to insert before serialized object when hashing + @param type Signing key type used to derive public key + @param sk Signing secret key + @param sigField Field in which to store the signature on the object. + If not specified the value defaults to `sfSignature`. + + @note If a signature already exists, it is overwritten. */ void -sign (STObject& st, - HashPrefix const& prefix, - KeyType type, SecretKey const& sk); +sign (STObject& st, HashPrefix const& prefix, + KeyType type, SecretKey const& sk, + SF_Blob const& sigField = sfSignature); -/** Verify the signature on a STObject. - The signature must be contained in sfSignature. +/** Returns `true` if STObject contains valid signature + + @param st Signed object + @param prefix Prefix inserted before serialized object when hashing + @param pk Public key for verifying signature + @param sigField Object's field containing the signature. + If not specified the value defaults to `sfSignature`. */ bool -verify (STObject const& st, - HashPrefix const& prefix, - PublicKey const& pk, - bool mustBeFullyCanonical); +verify (STObject const& st, HashPrefix const& prefix, + PublicKey const& pk, bool mustBeFullyCanonical, + SF_Blob const& sigField = sfSignature); /** Return a Serializer suitable for computing a multisigning TxnSignature. */ Serializer diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 2983da0cab..fa7fbff10e 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -199,9 +199,11 @@ SF_Blob const sfMemoType = make::one(&sfMemoType, STI SF_Blob const sfMemoData = make::one(&sfMemoData, STI_VL, 13, "MemoData"); SF_Blob const sfMemoFormat = make::one(&sfMemoFormat, STI_VL, 14, "MemoFormat"); + // variable length (uncommon) // 16 has not been used yet... -SF_Blob const sfProof = make::one(&sfProof, STI_VL, 17, "Proof"); +SF_Blob const sfProof = make::one(&sfProof, STI_VL, 17, "Proof"); +SF_Blob const sfMasterSignature = make::one(&sfMasterSignature, STI_VL, 18, "MasterSignature", SField::sMD_Default, SField::notSigning); // account SF_Account const sfAccount = make::one(&sfAccount, STI_ACCOUNT, 1, "Account"); diff --git a/src/ripple/protocol/impl/Sign.cpp b/src/ripple/protocol/impl/Sign.cpp index d5070603a6..6995865f3c 100644 --- a/src/ripple/protocol/impl/Sign.cpp +++ b/src/ripple/protocol/impl/Sign.cpp @@ -24,22 +24,22 @@ namespace ripple { void sign (STObject& st, HashPrefix const& prefix, - KeyType type, SecretKey const& sk) + KeyType type, SecretKey const& sk, + SF_Blob const& sigField) { Serializer ss; ss.add32(prefix); st.addWithoutSigningFields(ss); - set(st, sfSignature, + set(st, sigField, sign(type, sk, ss.slice())); } bool -verify (STObject const& st, - HashPrefix const& prefix, - PublicKey const& pk, - bool mustBeFullyCanonical) +verify (STObject const& st, HashPrefix const& prefix, + PublicKey const& pk, bool mustBeFullyCanonical, + SF_Blob const& sigField) { - auto const sig = get(st, sfSignature); + auto const sig = get(st, sigField); if (! sig) return false; Serializer ss; diff --git a/src/ripple/rpc/handlers/ValidationCreate.cpp b/src/ripple/rpc/handlers/ValidationCreate.cpp index 77a8d087c4..8265296009 100644 --- a/src/ripple/rpc/handlers/ValidationCreate.cpp +++ b/src/ripple/rpc/handlers/ValidationCreate.cpp @@ -52,11 +52,14 @@ Json::Value doValidationCreate (RPC::Context& context) if (!seed) return rpcError (rpcBAD_SEED); + auto const private_key = generateSecretKey (KeyType::secp256k1, *seed); + obj[jss::validation_public_key] = toBase58 ( TokenType::TOKEN_NODE_PUBLIC, - derivePublicKey ( - KeyType::secp256k1, - generateSecretKey (KeyType::secp256k1, *seed))); + derivePublicKey (KeyType::secp256k1, private_key)); + + obj[jss::validation_private_key] = toBase58 ( + TokenType::TOKEN_NODE_PRIVATE, private_key); obj[jss::validation_seed] = toBase58 (*seed); obj[jss::validation_key] = seedAs1751 (*seed); diff --git a/src/test/overlay/manifest_test.cpp b/src/test/overlay/manifest_test.cpp index 1a05a147bc..4330193ae3 100644 --- a/src/test/overlay/manifest_test.cpp +++ b/src/test/overlay/manifest_test.cpp @@ -102,18 +102,23 @@ public: Manifest make_Manifest - (KeyType type, SecretKey const& sk, PublicKey const& spk, int seq, - bool broken = false) + (SecretKey const& sk, KeyType type, SecretKey const& ssk, KeyType stype, + int seq, bool broken = false) { auto const pk = derivePublicKey(type, sk); + auto const spk = derivePublicKey(stype, ssk); STObject st(sfGeneric); st[sfSequence] = seq; st[sfPublicKey] = pk; st[sfSigningPubKey] = spk; - sign(st, HashPrefix::manifest, type, sk); - BEAST_EXPECT(verify(st, HashPrefix::manifest, pk, true)); + sign(st, HashPrefix::manifest, stype, ssk); + BEAST_EXPECT(verify(st, HashPrefix::manifest, spk, true)); + + sign(st, HashPrefix::manifest, type, sk, sfMasterSignature); + BEAST_EXPECT(verify( + st, HashPrefix::manifest, pk, true, sfMasterSignature)); if (broken) { @@ -227,7 +232,8 @@ public: auto const sk = randomSecretKey(); auto const kp = randomKeyPair(KeyType::secp256k1); - auto const m = make_Manifest (KeyType::ed25519, sk, kp.first, 0); + auto const m = make_Manifest ( + sk, KeyType::ed25519, kp.second, KeyType::secp256k1, 0); cache.configManifest (clone (m), *unl, journal); BEAST_EXPECT(cache.trusted (m.masterKey)); @@ -330,7 +336,8 @@ public: auto const sk = randomSecretKey(); auto const pk = derivePublicKey(KeyType::ed25519, sk); auto const kp = randomKeyPair(KeyType::secp256k1); - auto const m = make_Manifest (KeyType::ed25519, sk, kp.first, 0); + auto const m = make_Manifest ( + sk, KeyType::ed25519, kp.second, KeyType::secp256k1, 0); STObject st(sfGeneric); st[sfSequence] = 0; @@ -339,9 +346,11 @@ public: Serializer ss; ss.add32(HashPrefix::manifest); st.addWithoutSigningFields(ss); - auto const sig = sign(KeyType::ed25519, sk, ss.slice()); - + auto const sig = sign(KeyType::secp256k1, kp.second, ss.slice()); BEAST_EXPECT(strHex(sig) == strHex(m.getSignature())); + + auto const masterSig = sign(KeyType::ed25519, sk, ss.slice()); + BEAST_EXPECT(strHex(masterSig) == strHex(m.getMasterSignature())); } void @@ -360,16 +369,21 @@ public: auto const sk_a = randomSecretKey(); auto const pk_a = derivePublicKey(KeyType::ed25519, sk_a); auto const kp_a = randomKeyPair(KeyType::secp256k1); - auto const s_a0 = make_Manifest (KeyType::ed25519, sk_a, kp_a.first, 0); - auto const s_a1 = make_Manifest (KeyType::ed25519, sk_a, kp_a.first, 1); + auto const s_a0 = make_Manifest ( + sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 0); + auto const s_a1 = make_Manifest ( + sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 1); auto const sk_b = randomSecretKey(); auto const pk_b = derivePublicKey(KeyType::ed25519, sk_b); auto const kp_b = randomKeyPair(KeyType::secp256k1); - auto const s_b0 = make_Manifest (KeyType::ed25519, sk_b, kp_b.first, 0); - auto const s_b1 = make_Manifest (KeyType::ed25519, sk_b, kp_b.first, 1); - auto const s_b2 = - make_Manifest (KeyType::ed25519, sk_b, kp_b.first, 2, true); // broken + auto const s_b0 = make_Manifest ( + sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 0); + auto const s_b1 = make_Manifest ( + sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 1); + auto const s_b2 = make_Manifest ( + sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 2, + true); // broken auto const fake = s_b1.serialized + '\0'; BEAST_EXPECT(cache.applyManifest (clone (s_a0), *unl, journal) == untrusted); @@ -395,7 +409,8 @@ public: auto const sk_c = randomSecretKey(); auto const pk_c = derivePublicKey(KeyType::ed25519, sk_c); auto const kp_c = randomKeyPair(KeyType::secp256k1); - auto const s_c0 = make_Manifest (KeyType::ed25519, sk_c, kp_c.first, 0); + auto const s_c0 = make_Manifest ( + sk_c, KeyType::ed25519, kp_c.second, KeyType::secp256k1, 0); BEAST_EXPECT(unl->insertPermanentKey(pk_c, "trusted key")); BEAST_EXPECT(unl->trusted(pk_c)); BEAST_EXPECT(!cache.trusted(pk_c)); diff --git a/src/test/rpc/ServerInfo_test.cpp b/src/test/rpc/ServerInfo_test.cpp index 198f9a541c..febab8e313 100644 --- a/src/test/rpc/ServerInfo_test.cpp +++ b/src/test/rpc/ServerInfo_test.cpp @@ -31,15 +31,15 @@ namespace test { namespace validator { static auto const seed = "ss7t3J9dYentEFgKdPA3q6eyxtrLB"; static auto const master_key = - "nHU4LxxrSQsRTKy5uZbX95eYowoamUEPCcWraxoiCNbtDaUr1V34"; + "nHUYwQk8AyQ8pW9p4SvrWC2hosvaoii9X54uGLDYGBtEFwWFHsJK"; static auto const signing_key = "n9LHPLA36SBky1YjbaVEApQQ3s9XcpazCgfAG7jsqBb1ugDAosbm"; // Format manifest string to test trim() static auto const manifest = - " JAAAAAFxIe2FwblmJwz4pVYXHLJSzSBgIK7mpQuHNQ88CxW\n" - " \tjIN7q4nMhAuUTyasIhvj2KPfNRbmmIBnqNUzidgkKb244eP \n" - "\t794ZpMdkC+8l5n3R/CHP6SAwhYDOaqub0Cs2NjjewBnp1mf\n" - "\t 23rhAzdcjRuWzm0IT12eduZ0DwcF5Ng8rAelaYP1iT93ScE\t \t"; + " JAAAAAFxIe2cDLvm5IqpeGFlMTD98HCqv7+GE54anRD/zbvGNYtOsXMhAuUTyasIhvj2KPfN\n" + " \tRbmmIBnqNUzidgkKb244eP794ZpMdkYwRAIgNVq8SYP7js0C/GAGMKVYXiCGUTIL7OKPSBLS \n" + "\t7LTyrL4CIE+s4Tsn/FrrYj0nMEV1Mvf7PMRYCxtEERD3PG/etTJ3cBJAbwWWofHqg9IACoYV\n" + "\t +n9ulZHSVRajo55EkZYw0XUXDw8zcI4gD58suOSLZTG/dXtZp17huIyHgxHbR2YeYjQpCw==\t \t"; static auto sequence = 1; }