mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Add manifests subscriptions
Add verify method to manifest script to check signature
This commit is contained in:
committed by
Edward Hennis
parent
a67e4ab9f1
commit
749b4adc7c
@@ -22,6 +22,9 @@ Usage:
|
||||
sign <sequence> <validator-public> <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.
|
||||
"""
|
||||
|
||||
def prepend_length_byte(b):
|
||||
@@ -113,6 +116,16 @@ def get_signature(seq, validator_public_key_human, private_key_human):
|
||||
m1 = sign_manifest(m, private_key, 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):
|
||||
@@ -131,6 +144,12 @@ def perform_sign(
|
||||
print(wrap(get_signature(
|
||||
int(seq), validator_public_key_human, private_key_human)))
|
||||
|
||||
def perform_verify(
|
||||
seq, validator_public_key_human, public_key_human, signature, print=print):
|
||||
verify_signature(
|
||||
int(seq), validator_public_key_human, public_key_human, signature)
|
||||
print('Signature valid for', public_key_human)
|
||||
|
||||
# Externally visible versions of functions.
|
||||
def create():
|
||||
perform_create()
|
||||
@@ -141,6 +160,8 @@ def 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 usage(*errors):
|
||||
if errors:
|
||||
@@ -148,7 +169,7 @@ def usage(*errors):
|
||||
print(USAGE)
|
||||
return not errors
|
||||
|
||||
_COMMANDS = dict((f.__name__, f) for f in (create, check, sign))
|
||||
_COMMANDS = dict((f.__name__, f) for f in (create, check, sign, verify))
|
||||
|
||||
def run_command(args):
|
||||
if not args:
|
||||
|
||||
@@ -14,6 +14,10 @@ class test_Sign(TestCase):
|
||||
'JAAAABdxIe2DIKUZd9jDjKikknxnDfWCHkSXYZReFenvsmoVCdIw6nMhAnZ2dnZ2'
|
||||
'dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dkDOjlWtQSvRTjuwe+4iNusg0sJM'
|
||||
'zqkBJwDz30b2SkxZ7Fte/Vx4htM/kkfUfJCaxmxE5N4dHSKuiO9iDHsktqIA')
|
||||
VALIDATOR_KEY_HUMAN = 'n9JijuoCv8ubEy5ag3LiX3hyq27GaLJsitZPbQ6APkwx2MkUXq8E'
|
||||
SIGNATURE_HEX = (
|
||||
'0a1546caa29c887f9fcb5e6143ea101b31fb5895a5cdfa24939301c66ff51794'
|
||||
'a0b729e0ebbf576f2cc7cdb9f68c2366324a53b8e1ecf16f3c17bebbdb8d7102')
|
||||
|
||||
def setUp(self):
|
||||
self.results = []
|
||||
@@ -96,6 +100,11 @@ class test_Sign(TestCase):
|
||||
'dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dkDOjlWtQSvRTjuwe+4iNusg0sJM'
|
||||
'zqkBJwDz30b2SkxZ7Fte/Vx4htM/kkfUfJCaxmxE5N4dHSKuiO9iDHsktqIA')
|
||||
|
||||
def test_verify_signature(self):
|
||||
Sign.verify_signature(self.SEQUENCE, self.VALIDATOR_KEY_HUMAN,
|
||||
'nHUUaKHpxyRP4TZZ79tTpXuTpoM8pRNs5crZpGVA5jdrjib5easY',
|
||||
self.SIGNATURE_HEX)
|
||||
|
||||
def test_check(self):
|
||||
public = Base58.encode_version(Base58.VER_NODE_PRIVATE, 32 * 'k')
|
||||
Sign.perform_check(public, self.print)
|
||||
@@ -125,3 +134,8 @@ class test_Sign(TestCase):
|
||||
'Z2dnZ2dkDOjlWtQSvRTjuwe+4iNusg0sJMzqkBJwDz30b2S\n'
|
||||
'kxZ7Fte/Vx4htM/kkfUfJCaxmxE5N4dHSKuiO9iDHsktqIA'],
|
||||
{}]])
|
||||
|
||||
def test_verify(self):
|
||||
Sign.perform_verify(self.SEQUENCE, self.VALIDATOR_KEY_HUMAN,
|
||||
'nHUUaKHpxyRP4TZZ79tTpXuTpoM8pRNs5crZpGVA5jdrjib5easY',
|
||||
self.SIGNATURE_HEX, print=self.print)
|
||||
|
||||
@@ -436,6 +436,10 @@ public:
|
||||
bool subBook (InfoSub::ref ispListener, Book const&) override;
|
||||
bool unsubBook (std::uint64_t uListener, Book const&) override;
|
||||
|
||||
bool subManifests (InfoSub::ref ispListener) override;
|
||||
bool unsubManifests (std::uint64_t uListener) override;
|
||||
void pubManifest (Manifest const&) override;
|
||||
|
||||
bool subTransactions (InfoSub::ref ispListener) override;
|
||||
bool unsubTransactions (std::uint64_t uListener) override;
|
||||
|
||||
@@ -526,6 +530,7 @@ private:
|
||||
subRpcMapType mRpcSubMap;
|
||||
|
||||
SubMapType mSubLedger; // Accepted ledgers.
|
||||
SubMapType mSubManifests; // Received validator manifests.
|
||||
SubMapType mSubServer; // When server changes connectivity state.
|
||||
SubMapType mSubTransactions; // All accepted transactions.
|
||||
SubMapType mSubRTTransactions; // All proposed and accepted transactions.
|
||||
@@ -1507,6 +1512,36 @@ void NetworkOPsImp::consensusViewChange ()
|
||||
setMode (omCONNECTED);
|
||||
}
|
||||
|
||||
void NetworkOPsImp::pubManifest (Manifest const& mo)
|
||||
{
|
||||
// VFALCO consider std::shared_mutex
|
||||
ScopedLockType sl (mSubLock);
|
||||
|
||||
if (!mSubManifests.empty ())
|
||||
{
|
||||
Json::Value jvObj (Json::objectValue);
|
||||
|
||||
jvObj [jss::type] = "manifestReceived";
|
||||
jvObj [jss::master_key] = toBase58(TokenType::TOKEN_NODE_PUBLIC, mo.masterKey);
|
||||
jvObj [jss::signing_key] = toBase58(TokenType::TOKEN_NODE_PUBLIC, mo.signingKey);
|
||||
jvObj [jss::seq] = Json::UInt (mo.sequence);
|
||||
jvObj [jss::signature] = strHex (mo.getSignature ());
|
||||
|
||||
for (auto i = mSubManifests.begin (); i != mSubManifests.end (); )
|
||||
{
|
||||
if (auto p = i->second.lock())
|
||||
{
|
||||
p->send (jvObj, true);
|
||||
++i;
|
||||
}
|
||||
else
|
||||
{
|
||||
i = mSubManifests.erase (i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkOPsImp::pubServer ()
|
||||
{
|
||||
// VFALCO TODO Don't hold the lock across calls to send...make a copy of the
|
||||
@@ -1568,9 +1603,7 @@ void NetworkOPsImp::pubValidation (STValidation::ref val)
|
||||
|
||||
for (auto i = mSubValidations.begin (); i != mSubValidations.end (); )
|
||||
{
|
||||
InfoSub::pointer p = i->second.lock ();
|
||||
|
||||
if (p)
|
||||
if (auto p = i->second.lock())
|
||||
{
|
||||
p->send (jvObj, true);
|
||||
++i;
|
||||
@@ -2538,6 +2571,20 @@ bool NetworkOPsImp::unsubLedger (std::uint64_t uSeq)
|
||||
return mSubLedger.erase (uSeq);
|
||||
}
|
||||
|
||||
// <-- bool: true=added, false=already there
|
||||
bool NetworkOPsImp::subManifests (InfoSub::ref isrListener)
|
||||
{
|
||||
ScopedLockType sl (mSubLock);
|
||||
return mSubManifests.emplace (isrListener->getSeq (), isrListener).second;
|
||||
}
|
||||
|
||||
// <-- bool: true=erased, false=was not there
|
||||
bool NetworkOPsImp::unsubManifests (std::uint64_t uSeq)
|
||||
{
|
||||
ScopedLockType sl (mSubLock);
|
||||
return mSubManifests.erase (uSeq);
|
||||
}
|
||||
|
||||
// <-- bool: true=added, false=already there
|
||||
bool NetworkOPsImp::subServer (InfoSub::ref isrListener, Json::Value& jvResult,
|
||||
bool admin)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <ripple/basics/CountedObject.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/overlay/impl/Manifest.h>
|
||||
#include <ripple/resource/Consumer.h>
|
||||
#include <ripple/protocol/Book.h>
|
||||
#include <beast/threads/Stoppable.h>
|
||||
@@ -84,6 +85,10 @@ public:
|
||||
virtual bool subLedger (ref ispListener, Json::Value& jvResult) = 0;
|
||||
virtual bool unsubLedger (std::uint64_t uListener) = 0;
|
||||
|
||||
virtual bool subManifests (ref ispListener) = 0;
|
||||
virtual bool unsubManifests (std::uint64_t uListener) = 0;
|
||||
virtual void pubManifest (Manifest const&) = 0;
|
||||
|
||||
virtual bool subServer (ref ispListener, Json::Value& jvResult,
|
||||
bool admin) = 0;
|
||||
virtual bool unsubServer (std::uint64_t uListener) = 0;
|
||||
|
||||
@@ -56,6 +56,7 @@ InfoSub::~InfoSub ()
|
||||
m_source.unsubTransactions (mSeq);
|
||||
m_source.unsubRTTransactions (mSeq);
|
||||
m_source.unsubLedger (mSeq);
|
||||
m_source.unsubManifests (mSeq);
|
||||
m_source.unsubServer (mSeq);
|
||||
m_source.unsubValidations (mSeq);
|
||||
m_source.unsubPeerStatus (mSeq);
|
||||
|
||||
@@ -119,6 +119,14 @@ bool Manifest::revoked () const
|
||||
return sequence == std::numeric_limits<std::uint32_t>::max ();
|
||||
}
|
||||
|
||||
Blob Manifest::getSignature () const
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (serialized.data (), serialized.size ());
|
||||
st.set (sit);
|
||||
return st.getFieldVL (sfSignature);
|
||||
}
|
||||
|
||||
void
|
||||
ManifestCache::configValidatorKey(
|
||||
std::string const& line, beast::Journal journal)
|
||||
|
||||
@@ -98,6 +98,7 @@ struct Manifest
|
||||
bool verify () const;
|
||||
uint256 hash () const;
|
||||
bool revoked () const;
|
||||
Blob getSignature () const;
|
||||
};
|
||||
|
||||
boost::optional<Manifest> make_Manifest(std::string s);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/core/DatabaseCon.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
@@ -695,6 +696,10 @@ OverlayImpl::onManifests (
|
||||
app_.validators(),
|
||||
journal);
|
||||
|
||||
if (result == ManifestDisposition::accepted ||
|
||||
result == ManifestDisposition::untrusted)
|
||||
app_.getOPs().pubManifest (*make_Manifest(serialized));
|
||||
|
||||
if (result == ManifestDisposition::accepted)
|
||||
{
|
||||
auto db = app_.getWalletDB ().checkoutDb ();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/basics/TestSuite.h>
|
||||
#include <ripple/overlay/impl/Manifest.h>
|
||||
#include <ripple/core/DatabaseCon.h>
|
||||
@@ -93,9 +94,9 @@ public:
|
||||
auto const pk = derivePublicKey(type, sk);
|
||||
|
||||
STObject st(sfGeneric);
|
||||
set(st, sfSequence, seq);
|
||||
set(st, sfPublicKey, pk);
|
||||
set(st, sfSigningPubKey, spk);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = spk;
|
||||
|
||||
sign(st, HashPrefix::manifest, type, sk);
|
||||
expect(verify(st, HashPrefix::manifest, pk, true));
|
||||
@@ -179,6 +180,26 @@ public:
|
||||
boost::filesystem::path (dbName));
|
||||
}
|
||||
|
||||
void testGetSignature()
|
||||
{
|
||||
testcase ("getSignature");
|
||||
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);
|
||||
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = 0;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = kp.first;
|
||||
Serializer ss;
|
||||
ss.add32(HashPrefix::manifest);
|
||||
st.addWithoutSigningFields(ss);
|
||||
auto const sig = sign(KeyType::ed25519, sk, ss.slice());
|
||||
|
||||
expect (strHex(sig) == strHex(m.getSignature()));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -227,6 +248,7 @@ public:
|
||||
expect (cache.applyManifest (clone (s_b2), *unl, journal) == invalid);
|
||||
}
|
||||
testLoadStore (cache, *unl);
|
||||
testGetSignature ();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -341,6 +341,7 @@ JSS ( server_state ); // out: NetworkOPs
|
||||
JSS ( server_status ); // out: NetworkOPs
|
||||
JSS ( severity ); // in: LogLevel
|
||||
JSS ( signature ); // out: NetworkOPs
|
||||
JSS ( signing_key ); // out: NetworkOPs
|
||||
JSS ( signer_list ); // in: AccountObjects
|
||||
JSS ( snapshot ); // in: Subscribe
|
||||
JSS ( source_account ); // in: PathRequest, RipplePathFind
|
||||
|
||||
@@ -124,6 +124,10 @@ Json::Value doSubscribe (RPC::Context& context)
|
||||
{
|
||||
context.netOps.subLedger (ispSub, jvResult);
|
||||
}
|
||||
else if (streamName == "manifests")
|
||||
{
|
||||
context.netOps.subManifests (ispSub);
|
||||
}
|
||||
else if (streamName == "transactions")
|
||||
{
|
||||
context.netOps.subTransactions (ispSub);
|
||||
|
||||
@@ -77,6 +77,10 @@ Json::Value doUnsubscribe (RPC::Context& context)
|
||||
{
|
||||
context.netOps.unsubLedger (ispSub->getSeq ());
|
||||
}
|
||||
else if (streamName == "manifests")
|
||||
{
|
||||
context.netOps.unsubManifests (ispSub->getSeq ());
|
||||
}
|
||||
else if (streamName == "transactions")
|
||||
{
|
||||
context.netOps.unsubTransactions (ispSub->getSeq ());
|
||||
|
||||
@@ -170,6 +170,21 @@ suite('JSON-RPC', function() {
|
||||
});
|
||||
});
|
||||
|
||||
test('subscribe manifests', function(done) {
|
||||
var rippled_config = testutils.get_server_config(config);
|
||||
var client = jsonrpc.client("http://" + rippled_config.rpc_ip + ":" + rippled_config.rpc_port);
|
||||
var http_config = config.http_servers["zed"];
|
||||
|
||||
client.call('subscribe', [{
|
||||
'url' : "http://" + http_config.ip + ":" + http_config.port,
|
||||
'streams' : [ 'manifests' ],
|
||||
}], function (result) {
|
||||
assert(typeof result === 'object');
|
||||
assert(result.status === 'success');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('subscribe validations', function(done) {
|
||||
var rippled_config = testutils.get_server_config(config);
|
||||
var client = jsonrpc.client("http://" + rippled_config.rpc_ip + ":" + rippled_config.rpc_port);
|
||||
@@ -179,7 +194,6 @@ suite('JSON-RPC', function() {
|
||||
'url' : "http://" + http_config.ip + ":" + http_config.port,
|
||||
'streams' : [ 'validations' ],
|
||||
}], function (result) {
|
||||
// console.log(JSON.stringify(result, undefined, 2));
|
||||
assert(typeof result === 'object');
|
||||
assert(result.status === 'success');
|
||||
done();
|
||||
|
||||
Reference in New Issue
Block a user