diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -190,7 +190,40 @@ // Generate the delegation to the session key. DelegationBuilder dgb(peerData.proof.value()); - if (peerData.sessionKey.GetPubKey() != peerData.proof->getMaster()) { + CPubKey master = peerData.proof->getMaster(); + if (argsman.IsArgSet("-avadelegation")) { + Delegation dg; + CDataStream stream(ParseHex(argsman.GetArg("-avadelegation", "")), + SER_NETWORK, 0); + stream >> dg; + + DelegationState dg_state; + if (!dg.verify(dg_state, peerData.proof.value(), master)) { + switch (dg_state.GetResult()) { + case avalanche::DelegationResult::INCORRECT_PROOF: + error = _("the avalanche delegation does not match the " + "proof\n"); + return false; + case avalanche::DelegationResult::INVALID_SIGNATURE: + error = _("the avalanche delegation has invalid " + "signatures\n"); + return false; + default: + error = _("the avalanche delegation is invalid\n"); + return false; + } + } + + dgb.importDelegation(dg); + } + + if (peerData.sessionKey.GetPubKey() != master) { + if (!argsman.IsArgSet("-avamasterkey")) { + error = _("An avalanche proof is set but no master key " + "provided\n"); + return false; + } + dgb.addLevel(DecodeSecret(argsman.GetArg("-avamasterkey", "")), peerData.sessionKey.GetPubKey()); } diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1228,6 +1228,12 @@ strprintf("Mandatory cooldown between two avapoll (default: %u)", AVALANCHE_DEFAULT_COOLDOWN), ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); + argsman.AddArg( + "-avadelegation", + "Avalanche proof delegation to the session key used by this node " + "(default: none). Should be used in conjunction with -avaproof and " + "-avamasterkey", + ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); argsman.AddArg("-avaproof", "Avalanche proof to be used by this node (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); diff --git a/test/functional/abc_p2p_avalanche.py b/test/functional/abc_p2p_avalanche.py --- a/test/functional/abc_p2p_avalanche.py +++ b/test/functional/abc_p2p_avalanche.py @@ -9,6 +9,7 @@ from test_framework.key import ( ECKey, ECPubKey, + bytes_to_wif, ) from test_framework.mininode import P2PInterface, mininode_lock from test_framework.messages import ( @@ -354,15 +355,40 @@ int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, NODE_AVALANCHE) - self.log.info("Test the avahello signature") - quorum = get_quorum() - poll_node = quorum[0] + def check_avahello(args): + # Restart the node with the given args + self.restart_node(0, self.extra_args[0] + args) - avahello = poll_node.wait_for_avahello().hello + quorum = get_quorum() + poll_node = quorum[0] - avakey.set(bytes.fromhex(node.getavalanchekey())) - assert avakey.verify_schnorr( - avahello.sig, avahello.get_sighash(poll_node)) + avahello = poll_node.wait_for_avahello().hello + + avakey.set(bytes.fromhex(node.getavalanchekey())) + assert avakey.verify_schnorr( + avahello.sig, avahello.get_sighash(poll_node)) + + self.log.info( + "Test the avahello signature with a generated delegation") + check_avahello([ + "-avaproof={}".format(proof), + "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN" + ]) + + session_key = ECKey() + session_key.generate() + delegation = node.delegateavalancheproof( + proof, + bytes_to_wif(privkey.get_bytes()), + session_key.get_pubkey().get_bytes().hex(), + ) + + self.log.info("Test the avahello signature with a supplied delegation") + check_avahello([ + "-avaproof={}".format(proof), + "-avadelegation={}".format(delegation), + "-avamasterkey={}".format(bytes_to_wif(session_key.get_bytes())), + ]) if __name__ == '__main__': diff --git a/test/functional/abc_rpc_avalancheproof.py b/test/functional/abc_rpc_avalancheproof.py --- a/test/functional/abc_rpc_avalancheproof.py +++ b/test/functional/abc_rpc_avalancheproof.py @@ -6,7 +6,7 @@ from test_framework.avatools import get_stakes from test_framework.key import ECKey, bytes_to_wif -from test_framework.messages import AvalancheDelegation +from test_framework.messages import AvalancheDelegation, FromHex from test_framework.mininode import P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch @@ -215,6 +215,28 @@ match=ErrorMatch.PARTIAL_REGEX, ) + self.log.info("Bad delegation should be rejected at startup") + + def check_delegation_init_error(delegation, message): + node.assert_start_raises_init_error( + self.extra_args[0] + [ + "-avadelegation={}".format(delegation), + "-avaproof={}".format(proof), + "-avamasterkey={}".format( + bytes_to_wif(privkey.get_bytes())), + ], + expected_msg="Error: " + message, + ) + + bad_proof_id = AvalancheDelegation() + check_delegation_init_error(bad_proof_id.serialize().hex(), + "the avalanche delegation does not match the proof") + + bad_level_sig = FromHex(AvalancheDelegation(), delegation) + bad_level_sig.levels[-1].pubkey = bytes.fromhex(proof_master) + check_delegation_init_error(bad_level_sig.serialize().hex(), + "the avalanche delegation has invalid signatures") + if __name__ == '__main__': AvalancheProofTest().main()