diff --git a/cosmwasm/contracts/ntt-global-accountant/src/contract.rs b/cosmwasm/contracts/ntt-global-accountant/src/contract.rs index 1854b3da7..f2a086434 100644 --- a/cosmwasm/contracts/ntt-global-accountant/src/contract.rs +++ b/cosmwasm/contracts/ntt-global-accountant/src/contract.rs @@ -209,9 +209,9 @@ fn handle_observation( ) .map_err(|_| ContractError::MissingDestinationPeerRegistration(o.emitter_chain.into()))?; if destination_peer != sender { - // SECURITY defense-in-depth: - // should never get here due to strict registration ordering restrictions - // the lookups above would have failed instead + // SECURITY: + // ensure that both peers are cross-registered + // this prevents a rogue transceiver from registering with and altering the balance of an existing network bail!("peers are not cross-registered") } @@ -517,9 +517,9 @@ fn handle_ntt_vaa( .load(deps.storage, (destination_chain, source_peer, source_chain)) .map_err(|_| ContractError::MissingDestinationPeerRegistration(source_chain.into()))?; if destination_peer != sender { - // SECURITY defense-in-depth: - // should never get here due to strict registration ordering restrictions - // the lookups above would have failed instead + // SECURITY: + // ensure that both peers are cross-registered + // this prevents a rogue transceiver from registering with and altering the balance of an existing network bail!("peers are not cross-registered") } diff --git a/wormchain/contracts/tools/__tests__/test_ntt_accountant.ts b/wormchain/contracts/tools/__tests__/test_ntt_accountant.ts index b948b2cc2..058ed63bc 100644 --- a/wormchain/contracts/tools/__tests__/test_ntt_accountant.ts +++ b/wormchain/contracts/tools/__tests__/test_ntt_accountant.ts @@ -61,6 +61,7 @@ if (process.env.INIT_SIGNERS_KEYS_CSV === "undefined") { * h. Ensure a token sent to a destination chain without a known transceiver is rejected * i. Ensure a token sent to a destination chain without a matching transceiver is rejected * j. Ensure spoofed tokens for more than the outstanding amount rejects successfully + * k. Ensure a rogue endpoint cannot complete a transfer * 4. Relayers * a. Ensure a relayer registration is saved * b. Ensure a valid NTT transfer works @@ -111,6 +112,7 @@ const HUB_CHAIN = 2; const HUB_TRANSCEIVER = `0000000000000000000000000000000000000000000000000000000000000042`; const SPOKE_CHAIN_A = 4; const SPOKE_TRANSCEIVER_A = `0000000000000000000000000000000000000000000000000000000000000043`; +const ROGUE_TRANSCEIVER_A = `ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43`; const SPOKE_CHAIN_B = 5; const SPOKE_TRANSCEIVER_B = `0000000000000000000000000000000000000000000000000000000000000044`; const FAUX_HUB_CHAIN = 420; @@ -675,6 +677,28 @@ describe("NTT Global Accountant Tests", () => { "insufficient balance in source account: Overflow: Cannot Sub" ); }); + test("k. Ensure a rogue endpoint cannot complete a transfer", async () => { + { + // set faux spoke registration to legit hub but not vice-versa + { + const vaa = makeVAA( + SPOKE_CHAIN_A, + ROGUE_TRANSCEIVER_A, + `18fc67c2${chainToHex(HUB_CHAIN)}${HUB_TRANSCEIVER}` + ); + const result = await submitVAA(vaa); + expect(result.code).toEqual(0); + } + } + const vaa = makeVAA( + SPOKE_CHAIN_A, + ROGUE_TRANSCEIVER_A, + mockTransferPayload(8, 1, HUB_CHAIN) + ); + const result = await submitVAA(vaa); + expect(result.code).toEqual(5); + expect(result.rawLog).toMatch("peers are not cross-registered"); + }); }); describe("4. Relayers", () => { test("a. Ensure a relayer registration is saved", async () => {