diff --git a/src/avalanche/delegation.h b/src/avalanche/delegation.h --- a/src/avalanche/delegation.h +++ b/src/avalanche/delegation.h @@ -52,6 +52,7 @@ const DelegationId &getId() const { return dgid; } const LimitedProofId &getLimitedProofId() const { return limitedProofid; } const CPubKey &getProofMaster() const { return proofMaster; } + const CPubKey &getDelegatedPubkey() const; ProofId getProofId() const; diff --git a/src/avalanche/delegation.cpp b/src/avalanche/delegation.cpp --- a/src/avalanche/delegation.cpp +++ b/src/avalanche/delegation.cpp @@ -57,6 +57,13 @@ return limitedProofid.computeProofId(proofMaster); } +const CPubKey &Delegation::getDelegatedPubkey() const { + if (!levels.empty()) { + return levels.back().pubkey; + } + return proofMaster; +} + DelegationId Delegation::computeDelegationId() const { uint256 hash = getProofId(); reduceLevels(hash, levels); diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -284,19 +284,45 @@ return nullptr; } - const CPubKey masterPubKey = peerData->proof->getMaster(); - if (masterKey.GetPubKey() != masterPubKey) { - error = _("The master key does not match the proof public key."); - return nullptr; + std::unique_ptr dgb; + const CPubKey &masterPubKey = masterKey.GetPubKey(); + + if (argsman.IsArgSet("-avadelegation")) { + Delegation dg; + if (!Delegation::FromHex(dg, argsman.GetArg("-avadelegation", ""), + error)) { + // error is set by FromHex() + return nullptr; + } + + if (dg.getProofId() != peerData->proof->getId()) { + error = _("The delegation does not match the proof."); + return nullptr; + } + + if (masterPubKey != dg.getDelegatedPubkey()) { + error = _( + "The master key does not match the delegation public key."); + return nullptr; + } + + dgb = std::make_unique(dg); + } else { + if (masterPubKey != peerData->proof->getMaster()) { + error = + _("The master key does not match the proof public key."); + return nullptr; + } + + dgb = std::make_unique(*peerData->proof); } // Generate the delegation to the session key. - DelegationBuilder dgb(*peerData->proof); const CPubKey sessionPubKey = sessionKey.GetPubKey(); if (sessionPubKey != masterPubKey) { - dgb.addLevel(masterKey, sessionPubKey); + dgb->addLevel(masterKey, sessionPubKey); } - peerData->delegation = dgb.build(); + peerData->delegation = dgb->build(); if (!VerifyDelegation(peerData->delegation, sessionPubKey, error)) { // error is set by VerifyDelegation diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1229,6 +1229,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 master 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_peer_discovery.py b/test/functional/abc_p2p_avalanche_peer_discovery.py --- a/test/functional/abc_p2p_avalanche_peer_discovery.py +++ b/test/functional/abc_p2p_avalanche_peer_discovery.py @@ -82,14 +82,41 @@ int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, NODE_AVALANCHE) - self.log.info("Test the avahello signature (node -> P2PInterface)") - good_interface = get_ava_p2p_interface(node) - avahello = good_interface.wait_for_avahello().hello + def check_avahello(args): + # Restart the node with the given args + self.restart_node(0, self.extra_args[0] + args) + + peer = get_ava_p2p_interface(node) + + avahello = peer.wait_for_avahello().hello + + avakey = ECPubKey() + avakey.set(bytes.fromhex(node.getavalanchekey())) + assert avakey.verify_schnorr( + avahello.sig, avahello.get_sighash(peer)) + + self.log.info( + "Test the avahello signature with a generated delegation") + check_avahello([ + "-avaproof={}".format(proof), + "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN" + ]) - avakey = ECPubKey() - avakey.set(bytes.fromhex(node.getavalanchekey())) - assert avakey.verify_schnorr( - avahello.sig, avahello.get_sighash(good_interface)) + master_key = ECKey() + master_key.generate() + limited_id = FromHex(AvalancheProof(), proof).limited_proofid + delegation = node.delegateavalancheproof( + f"{limited_id:0{64}x}", + bytes_to_wif(privkey.get_bytes()), + master_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(master_key.get_bytes())), + ]) stakes = create_coinbase_stakes(node, [blockhashes[1]], addrkey0.key) interface_proof_hex = node.buildavalancheproof( @@ -120,6 +147,7 @@ self.log.info( 'Check that receiving a valid avahello triggers a proof getdata request') + good_interface = get_ava_p2p_interface(node) proofid = good_interface.send_avahello( interface_delegation_hex, delegated_key) 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 @@ -395,6 +395,39 @@ expected_msg="Error: The master key does not match the proof public key.", ) + 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(delegated_privkey.get_bytes())), + ], + expected_msg="Error: " + message, + ) + + check_delegation_init_error( + AvalancheDelegation().serialize().hex(), + "The delegation does not match the proof.") + + bad_level_sig = FromHex(AvalancheDelegation(), delegation) + # Tweak some key to cause the signature to mismatch + bad_level_sig.levels[-2].pubkey = bytes.fromhex(proof_master) + check_delegation_init_error(bad_level_sig.serialize().hex(), + "The avalanche delegation has invalid signatures.") + + node.assert_start_raises_init_error( + self.extra_args[0] + [ + "-avadelegation={}".format(delegation), + "-avaproof={}".format(proof), + "-avamasterkey={}".format( + bytes_to_wif(random_privkey.get_bytes())), + ], + expected_msg="Error: The master key does not match the delegation public key.", + ) + if __name__ == '__main__': AvalancheProofTest().main()