Sign manifest with ephemeral and master keys (RIPD-1083)

This commit is contained in:
wilsonianb
2016-02-03 14:28:12 -08:00
committed by Edward Hennis
parent 7d46d153c6
commit b55edfa8f0
15 changed files with 262 additions and 182 deletions

View File

@@ -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.

View File

@@ -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)

View File

@@ -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:

View File

@@ -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)

View File

@@ -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`

View File

@@ -42,7 +42,8 @@ make_Manifest (std::string s)
auto const opt_spk = get<PublicKey>(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<std::runtime_error> ("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);

View File

@@ -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<Manifest> make_Manifest(std::string s);

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -199,9 +199,11 @@ SF_Blob const sfMemoType = make::one<SF_Blob::type>(&sfMemoType, STI
SF_Blob const sfMemoData = make::one<SF_Blob::type>(&sfMemoData, STI_VL, 13, "MemoData");
SF_Blob const sfMemoFormat = make::one<SF_Blob::type>(&sfMemoFormat, STI_VL, 14, "MemoFormat");
// variable length (uncommon)
// 16 has not been used yet...
SF_Blob const sfProof = make::one<SF_Blob::type>(&sfProof, STI_VL, 17, "Proof");
SF_Blob const sfProof = make::one<SF_Blob::type>(&sfProof, STI_VL, 17, "Proof");
SF_Blob const sfMasterSignature = make::one<SF_Blob::type>(&sfMasterSignature, STI_VL, 18, "MasterSignature", SField::sMD_Default, SField::notSigning);
// account
SF_Account const sfAccount = make::one<SF_Account::type>(&sfAccount, STI_ACCOUNT, 1, "Account");

View File

@@ -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;

View File

@@ -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);

View File

@@ -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));

View File

@@ -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;
}