Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable tss auth on spl whitelisting #60

Merged
merged 5 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.anchor/
/target
**/.DS_Store
node_modules
109 changes: 104 additions & 5 deletions programs/protocol-contracts-solana/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,75 @@ pub mod gateway {
Ok(())
}

// whitelisting SPL tokens
pub fn whitelist_spl_mint(_ctx: Context<Whitelist>) -> Result<()> {
// whitelist new spl token
// in case signature is provided, check if tss is the signer, otherwise check if authority is pda.authority
// if succeeds, new whitelist entry account is created
pub fn whitelist_spl_mint(
skosito marked this conversation as resolved.
Show resolved Hide resolved
skosito marked this conversation as resolved.
Show resolved Hide resolved
ctx: Context<Whitelist>,
signature: [u8; 64],
recovery_id: u8,
message_hash: [u8; 32],
nonce: u64,
) -> Result<()> {
let pda = &mut ctx.accounts.pda;
let whitelist_candidate = &mut ctx.accounts.whitelist_candidate;
let authority = &ctx.accounts.authority;

// signature provided, recover and verify that tss is the signer
if signature != [0u8; 64] {
validate_whitelist_tss_signature(
pda,
whitelist_candidate,
signature,
recovery_id,
message_hash,
nonce,
"whitelist_spl_mint",
)?;
} else {
// no signature provided, fallback to authority check
require!(
authority.key() == pda.authority,
Errors::SignerIsNotAuthority
);
}

Ok(())
}

pub fn unwhitelist_spl_mint(_ctx: Context<Unwhitelist>) -> Result<()> {
// unwhitelist new spl token
// in case signature is provided, check if tss is the signer, otherwise check if authority is pda.authority
// if succeeds, whitelist entry account is deleted
pub fn unwhitelist_spl_mint(
ctx: Context<Unwhitelist>,
signature: [u8; 64],
recovery_id: u8,
message_hash: [u8; 32],
nonce: u64,
) -> Result<()> {
let pda = &mut ctx.accounts.pda;
let whitelist_candidate: &mut Account<'_, Mint> = &mut ctx.accounts.whitelist_candidate;
let authority = &ctx.accounts.authority;

// signature provided, recover and verify that tss is the signer
if signature != [0u8; 64] {
validate_whitelist_tss_signature(
pda,
whitelist_candidate,
signature,
recovery_id,
message_hash,
nonce,
"unwhitelist_spl_mint",
)?;
} else {
// no signature provided, fallback to authority check
require!(
authority.key() == pda.authority,
Errors::SignerIsNotAuthority
);
}

Ok(())
}

Expand Down Expand Up @@ -393,6 +456,42 @@ fn recover_eth_address(
Ok(eth_address)
}

// recover and verify tss signature for whitelist and unwhitelist instructions
fn validate_whitelist_tss_signature(
pda: &mut Account<Pda>,
whitelist_candidate: &mut Account<Mint>,
signature: [u8; 64],
recovery_id: u8,
message_hash: [u8; 32],
nonce: u64,
instruction_name: &str,
) -> Result<()> {
if nonce != pda.nonce {
msg!("mismatch nonce");
return err!(Errors::NonceMismatch);
}

let mut concatenated_buffer = Vec::new();
concatenated_buffer.extend_from_slice(instruction_name.as_bytes());
concatenated_buffer.extend_from_slice(&pda.chain_id.to_be_bytes());
concatenated_buffer.extend_from_slice(&whitelist_candidate.key().to_bytes());
concatenated_buffer.extend_from_slice(&nonce.to_be_bytes());
require!(
message_hash == hash(&concatenated_buffer[..]).to_bytes(),
Errors::MessageHashMismatch
);

let address = recover_eth_address(&message_hash, recovery_id, &signature)?;
if address != pda.tss_address {
msg!("ECDSA signature error");
return err!(Errors::TSSAuthenticationFailed);
}

pda.nonce += 1;

Ok(())
}

#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
Expand Down Expand Up @@ -513,7 +612,7 @@ pub struct Whitelist<'info> {
pub whitelist_entry: Account<'info, WhitelistEntry>,
pub whitelist_candidate: Account<'info, Mint>,

#[account(mut, seeds = [b"meta"], bump, has_one = authority)]
#[account(mut, seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,
#[account(mut)]
pub authority: Signer<'info>,
Expand All @@ -535,7 +634,7 @@ pub struct Unwhitelist<'info> {
pub whitelist_entry: Account<'info, WhitelistEntry>,
pub whitelist_candidate: Account<'info, Mint>,

#[account(mut, seeds = [b"meta"], bump, has_one = authority)]
#[account(mut, seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,
#[account(mut)]
pub authority: Signer<'info>,
Expand Down
76 changes: 67 additions & 9 deletions tests/protocol-contracts-solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe("some tests", () => {
})

it("whitelist USDC spl token", async () => {
await gatewayProgram.methods.whitelistSplMint().accounts({
await gatewayProgram.methods.whitelistSplMint([], 0, [], new anchor.BN(0)).accounts({
whitelistCandidate: mint.publicKey,
}).signers([]).rpc();

Expand All @@ -199,15 +199,14 @@ describe("some tests", () => {
seeds,
gatewayProgram.programId,
);
let entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress)

try {
seeds = [Buffer.from("whitelist", "utf-8"), mint_fake.publicKey.toBuffer()];
[entryAddress] = anchor.web3.PublicKey.findProgramAddressSync(
seeds,
gatewayProgram.programId,
);
entry = await gatewayProgram.account.whitelistEntry.fetch(entryAddress);
await gatewayProgram.account.whitelistEntry.fetch(entryAddress);
} catch(err) {
expect(err.message).to.include("Account does not exist or has no data");
}
Expand Down Expand Up @@ -289,7 +288,6 @@ describe("some tests", () => {
// expect(account2.amount).to.be.eq(1_000_000n);

const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount);
const hexAddr = bufferToHex(Buffer.from(pdaAccountData.tssAddress));
const amount = new anchor.BN(500_000);
const nonce = pdaAccountData.nonce;
await withdrawSplToken(mint, usdcDecimals, amount, nonce, pda_ata, wallet_ata, wallet.publicKey, keyPair, gatewayProgram);
Expand Down Expand Up @@ -414,7 +412,7 @@ describe("some tests", () => {
})

it("unwhitelist SPL token and deposit should fail", async () => {
await gatewayProgram.methods.unwhitelistSplMint().accounts({
await gatewayProgram.methods.unwhitelistSplMint([], 0, [], new anchor.BN(0)).accounts({
whitelistCandidate: mint.publicKey,
}).rpc();

Expand All @@ -427,12 +425,75 @@ describe("some tests", () => {
});

it("re-whitelist SPL token and deposit should succeed", async () => {
await gatewayProgram.methods.whitelistSplMint().accounts({
await gatewayProgram.methods.whitelistSplMint([], 0, [], new anchor.BN(0)).accounts({
whitelistCandidate: mint.publicKey,
}).rpc();
await depositSplTokens(gatewayProgram, conn, wallet, mint, address);
});

it("unwhitelist SPL token using TSS signature and deposit should fail", async () => {
const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount);
const nonce = pdaAccountData.nonce;

const buffer = Buffer.concat([
Buffer.from("unwhitelist_spl_mint","utf-8"),
chain_id_bn.toArrayLike(Buffer, 'be', 8),
mint.publicKey.toBuffer(),
nonce.toArrayLike(Buffer, 'be', 8),
]);
const message_hash = keccak256(buffer);
const signature = keyPair.sign(message_hash, 'hex');
const { r, s, recoveryParam } = signature;
const signatureBuffer = Buffer.concat([
r.toArrayLike(Buffer, 'be', 32),
s.toArrayLike(Buffer, 'be', 32),
]);

await gatewayProgram.methods.unwhitelistSplMint(
Array.from(signatureBuffer),
Number(recoveryParam),
Array.from(message_hash),
nonce,
).accounts({
whitelistCandidate: mint.publicKey,
}).rpc();

try {
await depositSplTokens(gatewayProgram, conn, wallet, mint, address)
} catch (err) {
expect(err).to.be.instanceof(anchor.AnchorError);
expect(err.message).to.include("AccountNotInitialized");
}
});

it("re-whitelist SPL token using TSS signature and deposit should succeed", async () => {
const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount);
const nonce = pdaAccountData.nonce;

const buffer = Buffer.concat([
Buffer.from("whitelist_spl_mint","utf-8"),
chain_id_bn.toArrayLike(Buffer, 'be', 8),
mint.publicKey.toBuffer(),
nonce.toArrayLike(Buffer, 'be', 8),
]);
const message_hash = keccak256(buffer);
const signature = keyPair.sign(message_hash, 'hex');
const { r, s, recoveryParam } = signature;
const signatureBuffer = Buffer.concat([
r.toArrayLike(Buffer, 'be', 32),
s.toArrayLike(Buffer, 'be', 32),
]);

await gatewayProgram.methods.whitelistSplMint(
Array.from(signatureBuffer),
Number(recoveryParam),
Array.from(message_hash),
nonce,
).accounts({
whitelistCandidate: mint.publicKey,
}).rpc();
await depositSplTokens(gatewayProgram, conn, wallet, mint, address);
});

it("update TSS address", async () => {
const newTss = new Uint8Array(20);
Expand Down Expand Up @@ -470,9 +531,6 @@ describe("some tests", () => {
}
});




const newAuthority = anchor.web3.Keypair.generate();
it("update authority", async () => {
await gatewayProgram.methods.updateAuthority(newAuthority.publicKey).accounts({
Expand Down
Loading