diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -137,6 +137,71 @@ return true; } +static bool VerifyProof(const Proof &proof, bilingual_str &error) { + ProofValidationState proof_state; + + if (!proof.verify(proof_state)) { + switch (proof_state.GetResult()) { + case ProofValidationResult::NO_STAKE: + error = _("the avalanche proof has no stake"); + return false; + case ProofValidationResult::DUST_THRESOLD: + error = _("the avalanche proof stake is too low"); + return false; + case ProofValidationResult::DUPLICATE_STAKE: + error = _("the avalanche proof has duplicated stake"); + return false; + case ProofValidationResult::INVALID_SIGNATURE: + error = _("the avalanche proof has invalid stake signatures"); + return false; + case ProofValidationResult::TOO_MANY_UTXOS: + error = strprintf( + _("the avalanche proof has too many utxos (max: %u)"), + AVALANCHE_MAX_PROOF_STAKES); + return false; + default: + error = _("the avalanche proof is invalid"); + return false; + } + } + + return true; +} + +static bool VerifyDelegation(const Delegation &dg, const Proof &proof, + const CPubKey &sessionPubKey, + bilingual_str &error) { + DelegationState dg_state; + CPubKey auth; + + if (!dg.verify(dg_state, proof, auth)) { + switch (dg_state.GetResult()) { + case avalanche::DelegationResult::INCORRECT_PROOF: + error = _("the avalanche delegation does not match the proof"); + return false; + case avalanche::DelegationResult::INVALID_SIGNATURE: + error = _("the avalanche delegation has invalid signatures"); + return false; + default: + error = _("the avalanche delegation is invalid"); + return false; + } + } + + // If the session key and the master key are identical we did not use + // the master key to sign a new delegation level. It is then possible + // that both the master and session are identical valid keys but none + // match the proof nor delegation. In this edge case the delegation + // might verify with no error but the node authentication public key + // will not match the session public key. + if (auth != sessionPubKey) { + error = _("the node is not allowed to use this proof or delegation"); + return false; + } + + return true; +} + struct Processor::PeerData { Proof proof; Delegation delegation; @@ -222,31 +287,9 @@ stream >> peerData->proof; } - ProofValidationState proof_state; - if (!peerData->proof.verify(proof_state)) { - switch (proof_state.GetResult()) { - case ProofValidationResult::NO_STAKE: - error = _("the avalanche proof has no stake"); - return nullptr; - case ProofValidationResult::DUST_THRESOLD: - error = _("the avalanche proof stake is too low"); - return nullptr; - case ProofValidationResult::DUPLICATE_STAKE: - error = _("the avalanche proof has duplicated stake"); - return nullptr; - case ProofValidationResult::INVALID_SIGNATURE: - error = - _("the avalanche proof has invalid stake signatures"); - return nullptr; - case ProofValidationResult::TOO_MANY_UTXOS: - error = strprintf( - _("the avalanche proof has too many utxos (max: %u)"), - AVALANCHE_MAX_PROOF_STAKES); - return nullptr; - default: - error = _("the avalanche proof is invalid"); - return nullptr; - } + if (!VerifyProof(peerData->proof, error)) { + // error is set by VerifyProof + return nullptr; } DelegationBuilder dgb(peerData->proof); @@ -263,11 +306,24 @@ } } - // Generate the delegation to the session key. - if (sessionKey.GetPubKey() != peerData->proof.getMaster()) { - dgb.addLevel(masterKey, sessionKey.GetPubKey()); + const CPubKey &masterPubKey = masterKey.GetPubKey(); + const CPubKey &sessionPubKey = sessionKey.GetPubKey(); + + // Generate the delegation to the session key if it differs from our + // current master + if (sessionPubKey != masterPubKey && + !dgb.addLevel(masterKey, sessionPubKey)) { + error = _("failed to sign the delegation for this session"); + return nullptr; } peerData->delegation = dgb.build(); + + CPubKey nodeAuth; + if (!VerifyDelegation(peerData->delegation, peerData->proof, + sessionPubKey, error)) { + // error is set by VerifyDelegation + return nullptr; + } } // We can't use std::make_unique with a private constructor 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 @@ -254,6 +254,35 @@ check_delegation_init_error(bad_proof_id.serialize().hex(), "the supplied avalanche delegation is invalid") + # Master private key mismatch + check_delegation_init_error(delegation, + "failed to sign the delegation for this session") + + 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") + + # Edge case of master key and session being identical, valid but not + # matching the pubkey, with or without a delegation. + node.assert_start_raises_init_error( + self.extra_args[0] + [ + "-avaproof={}".format(proof), + "-avamasterkey=cVuKKa7gbehEQvVq717hYcbE9Dqmq7KEBKqWgWrYBa2CKKrhtRim", + "-avasessionkey=cVuKKa7gbehEQvVq717hYcbE9Dqmq7KEBKqWgWrYBa2CKKrhtRim", + ], + expected_msg="Error: the node is not allowed to use this proof or delegation", + ) + node.assert_start_raises_init_error( + self.extra_args[0] + [ + "-avadelegation={}".format(delegation), + "-avaproof={}".format(proof), + "-avamasterkey=cVuKKa7gbehEQvVq717hYcbE9Dqmq7KEBKqWgWrYBa2CKKrhtRim", + "-avasessionkey=cVuKKa7gbehEQvVq717hYcbE9Dqmq7KEBKqWgWrYBa2CKKrhtRim", + ], + expected_msg="Error: the node is not allowed to use this proof or delegation", + ) + if __name__ == '__main__': AvalancheProofTest().main()