From 8987a4d9ef7a8ed5c22a21beeb41c873d989fc4b Mon Sep 17 00:00:00 2001 From: Fawaz Date: Mon, 11 May 2026 19:48:45 -0400 Subject: [PATCH 1/2] Add beacon_bind Arcium circuit + anonbeta1 program with MPC binding - beacon_bind circuit: SHA3-256 commitment of rns_dest_hash + region_code computed inside Arcium MPC, returns encrypted u128 to owner - anonbeta1 program: register_beacon, heartbeat (plain), plus 3 new Arcium instructions (init_comp_def, register_beacon_private, callback) - PrivateBeaconRegistry PDA for encrypted beacon state - Deployed to devnet: anon7uu8UtVoFgS8GCSfw2RqyphJhkN3xEjgPwznYDe - MXE initialized, comp def registered on-chain (cluster offset 456) - Circuit uploaded to Supabase storage bucket --- .gitignore | 10 ++ Anchor.toml | 4 +- Cargo.lock | 11 ++ encrypted-ixs/src/lib.rs | 29 ++++ init-beacon-bind-comp-def.ts | 110 ++++++++++++++ programs/anonbeta1/Cargo.toml | 32 ++++ programs/anonbeta1/src/constants.rs | 3 + programs/anonbeta1/src/errors.rs | 17 +++ programs/anonbeta1/src/events.rs | 24 +++ .../src/instructions/beacon_bind_callback.rs | 53 +++++++ .../anonbeta1/src/instructions/heartbeat.rs | 43 ++++++ .../instructions/init_beacon_bind_comp_def.rs | 42 ++++++ programs/anonbeta1/src/instructions/mod.rs | 5 + .../src/instructions/register_beacon.rs | 62 ++++++++ .../instructions/register_beacon_private.rs | 125 ++++++++++++++++ programs/anonbeta1/src/lib.rs | 66 +++++++++ programs/anonbeta1/src/state.rs | 35 +++++ yarn.lock | 139 ++++++++++-------- 18 files changed, 744 insertions(+), 66 deletions(-) create mode 100644 init-beacon-bind-comp-def.ts create mode 100644 programs/anonbeta1/Cargo.toml create mode 100644 programs/anonbeta1/src/constants.rs create mode 100644 programs/anonbeta1/src/errors.rs create mode 100644 programs/anonbeta1/src/events.rs create mode 100644 programs/anonbeta1/src/instructions/beacon_bind_callback.rs create mode 100644 programs/anonbeta1/src/instructions/heartbeat.rs create mode 100644 programs/anonbeta1/src/instructions/init_beacon_bind_comp_def.rs create mode 100644 programs/anonbeta1/src/instructions/mod.rs create mode 100644 programs/anonbeta1/src/instructions/register_beacon.rs create mode 100644 programs/anonbeta1/src/instructions/register_beacon_private.rs create mode 100644 programs/anonbeta1/src/lib.rs create mode 100644 programs/anonbeta1/src/state.rs diff --git a/.gitignore b/.gitignore index 7836312..6f9a97f 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,13 @@ tmp/ temp/ *.js !migrations/*.js + +# Working specs (do not commit — internal scratch docs) +ANONBETA1_SPEC.md + +# Keypairs — ALL Solana keypairs must be gitignored (defense in depth) +*testpair*.json +*-keypair.json +*_keypair.json +id.json +solana-*.json diff --git a/Anchor.toml b/Anchor.toml index 894b03c..ebdd7ff 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -6,9 +6,11 @@ resolution = true skip-lint = false [programs.devnet] +anonbeta1 = "anon7uu8UtVoFgS8GCSfw2RqyphJhkN3xEjgPwznYDe" ble_revshare = "7xeQNUggKc2e5q6AQxsFBLBkXGg2p54kSx11zVainMks" [programs.localnet] +anonbeta1 = "anon7uu8UtVoFgS8GCSfw2RqyphJhkN3xEjgPwznYDe" ble_revshare = "7xeQNUggKc2e5q6AQxsFBLBkXGg2p54kSx11zVainMks" [registry] @@ -16,7 +18,7 @@ url = "https://api.apr.dev" [provider] cluster = "devnet" -wallet = "~/.config/solana/id.json" +wallet = "./anontestpair_01.json" [scripts] test = "ARCIUM_CLUSTER_OFFSET=456 node --import tsx --test 'tests/**/*.ts'" diff --git a/Cargo.lock b/Cargo.lock index 284bf5b..06d8069 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,6 +316,17 @@ dependencies = [ "libc", ] +[[package]] +name = "anonbeta1" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "arcium-anchor", + "arcium-client", + "arcium-macros", + "blake3", +] + [[package]] name = "anyhow" version = "1.0.100" diff --git a/encrypted-ixs/src/lib.rs b/encrypted-ixs/src/lib.rs index 4fab37c..73cfeb6 100644 --- a/encrypted-ixs/src/lib.rs +++ b/encrypted-ixs/src/lib.rs @@ -15,4 +15,33 @@ mod circuits { let payment = payment_input.to_arcis(); payment_input.owner.from_arcis(payment.amount) } + + pub struct BindingInput { + rns_dest_hash: u128, + region_code: u32, + } + + #[instruction] + pub fn beacon_bind( + input: Enc, + ) -> Enc { + let b = input.to_arcis(); + let rns_bytes = b.rns_dest_hash.to_le_bytes(); + let region_bytes = b.region_code.to_le_bytes(); + let mut msg = [0u8; 20]; + for i in 0..16 { + msg[i] = rns_bytes[i]; + } + for i in 0..4 { + msg[16 + i] = region_bytes[i]; + } + let hash = SHA3_256::new().digest(&msg); + let mut commitment: u128 = 0; + let mut shift: u128 = 1; + for i in 0..16 { + commitment = commitment + (hash[i] as u128) * shift; + shift = shift * 256; + } + input.owner.from_arcis(commitment) + } } diff --git a/init-beacon-bind-comp-def.ts b/init-beacon-bind-comp-def.ts new file mode 100644 index 0000000..1c855d9 --- /dev/null +++ b/init-beacon-bind-comp-def.ts @@ -0,0 +1,110 @@ +import * as fs from 'fs'; +import * as anchor from '@coral-xyz/anchor'; +import BN from 'bn.js'; +import { + getCompDefAccOffset, + getMXEAccAddress, + getArciumAccountBaseSeed, + getArciumProgramId, + getLookupTableAddress, + getArciumProgram, +} from '@arcium-hq/client'; +import { PublicKey, AddressLookupTableProgram } from '@solana/web3.js'; + +const CIRCUIT_ID = 'beacon_bind'; + +async function initBeaconBindCompDef() { + console.log('Starting beacon_bind computation definition init...'); + + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + const program = anchor.workspace.Anonbeta1 as anchor.Program; + console.log('Program ID:', program.programId.toString()); + + const walletPath = process.env.ANCHOR_WALLET || './anontestpair_01.json'; + const owner = anchor.web3.Keypair.fromSecretKey( + new Uint8Array(JSON.parse(fs.readFileSync(walletPath, 'utf8'))) + ); + console.log('Owner:', owner.publicKey.toString()); + + const baseSeedCompDefAcc = getArciumAccountBaseSeed("ComputationDefinitionAccount"); + const offsetUint8Array = getCompDefAccOffset(CIRCUIT_ID); + const offsetBuffer = Buffer.from(offsetUint8Array); + + const [compDefPDA] = PublicKey.findProgramAddressSync( + [baseSeedCompDefAcc, program.programId.toBuffer(), offsetBuffer], + getArciumProgramId() + ); + + console.log('Comp def PDA:', compDefPDA.toString()); + console.log('Offset:', Buffer.from(offsetUint8Array).readUInt32LE(0)); + + const mxeAddress = getMXEAccAddress(program.programId); + console.log('MXE address:', mxeAddress.toString()); + + let lutOffsetSlot = new BN(0); + try { + const arciumProg = getArciumProgram(provider); + const mxeData = await arciumProg.account['mxeAccount'].fetch(mxeAddress); + lutOffsetSlot = (mxeData as any).lutOffsetSlot; + console.log('LUT offset slot:', lutOffsetSlot.toString()); + } catch (e) { + console.warn('Could not fetch lutOffsetSlot, using 0:', e); + } + + const addressLookupTable = getLookupTableAddress(program.programId, lutOffsetSlot); + console.log('LUT address:', addressLookupTable.toString()); + + const existingAccount = await provider.connection.getAccountInfo(compDefPDA); + if (existingAccount) { + console.log('Comp def account already exists at:', compDefPDA.toString()); + console.log('Skipping init (already done).'); + return; + } + + try { + const tx = await program.methods + .initBeaconBindCompDef() + .accounts({ + compDefAccount: compDefPDA, + payer: owner.publicKey, + mxeAccount: mxeAddress, + addressLookupTable, + lutProgram: AddressLookupTableProgram.programId, + }) + .signers([owner]) + .transaction(); + + const { blockhash, lastValidBlockHeight } = await provider.connection.getLatestBlockhash('confirmed'); + tx.recentBlockhash = blockhash; + tx.feePayer = owner.publicKey; + tx.sign(owner); + + const sim = await provider.connection.simulateTransaction(tx); + console.log('Simulation logs:', sim.value.logs); + if (sim.value.err) { + throw new Error(`Simulation failed: ${JSON.stringify(sim.value.err)}\nLogs:\n${sim.value.logs?.join('\n')}`); + } + + const rawTx = tx.serialize(); + const sig = await provider.connection.sendRawTransaction(rawTx, { skipPreflight: true }); + console.log('Sent tx:', sig); + await provider.connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed'); + console.log('Init comp def tx confirmed:', sig); + } catch (err: any) { + console.error('Failed:', err.message); + if (err.logs) { + console.error('Logs:'); + err.logs.forEach((log: string) => console.error(' ', log)); + } + process.exit(1); + } + + console.log('beacon_bind computation definition initialized.'); +} + +initBeaconBindCompDef().catch((error) => { + console.error('Fatal:', error); + process.exit(1); +}); diff --git a/programs/anonbeta1/Cargo.toml b/programs/anonbeta1/Cargo.toml new file mode 100644 index 0000000..dd6d77b --- /dev/null +++ b/programs/anonbeta1/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "anonbeta1" +version = "0.1.0" +description = "Anonmesh beacon registry program" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "anonbeta1" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["anchor-lang/idl-build", "arcium-anchor/idl-build"] +anchor-debug = [] +custom-heap = [] +custom-panic = [] + +[dependencies] +anchor-lang = { version = "0.32.1", features = ["init-if-needed"] } +arcium-client = { version = "=0.9.3", default-features = false } +arcium-macros = { version = "=0.9.3" } +arcium-anchor = { version = "=0.9.3" } +blake3 = "=1.8.2" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(target_os, values("solana"))', +] } diff --git a/programs/anonbeta1/src/constants.rs b/programs/anonbeta1/src/constants.rs new file mode 100644 index 0000000..f4190fc --- /dev/null +++ b/programs/anonbeta1/src/constants.rs @@ -0,0 +1,3 @@ +use arcium_anchor::prelude::comp_def_offset; + +pub const COMP_DEF_OFFSET_BEACON_BIND: u32 = comp_def_offset("beacon_bind"); diff --git a/programs/anonbeta1/src/errors.rs b/programs/anonbeta1/src/errors.rs new file mode 100644 index 0000000..af3a002 --- /dev/null +++ b/programs/anonbeta1/src/errors.rs @@ -0,0 +1,17 @@ +use anchor_lang::prelude::*; + +#[error_code] +pub enum ErrorCode { + #[msg("Reticulum dest hash cannot be all zeros")] + InvalidRnsHash, + #[msg("Region code must be printable ASCII")] + InvalidRegionCode, + #[msg("Operator does not match registered beacon")] + OperatorMismatch, + #[msg("Heartbeat counter overflow")] + HeartbeatOverflow, + #[msg("Cluster not set")] + ClusterNotSet, + #[msg("Computation was aborted or verification failed")] + AbortedComputation, +} diff --git a/programs/anonbeta1/src/events.rs b/programs/anonbeta1/src/events.rs new file mode 100644 index 0000000..7205078 --- /dev/null +++ b/programs/anonbeta1/src/events.rs @@ -0,0 +1,24 @@ +use anchor_lang::prelude::*; + +#[event] +pub struct BeaconRegistered { + pub operator: Pubkey, + pub beacon_pda: Pubkey, + pub rns_dest_hash: [u8; 16], + pub region_code: [u8; 4], + pub registered_at: i64, +} + +#[event] +pub struct BeaconHeartbeat { + pub operator: Pubkey, + pub beacon_pda: Pubkey, + pub heartbeat_count: u64, + pub timestamp: i64, +} + +#[event] +pub struct BeaconBindCompleted { + pub commitment_ciphertext: [u8; 32], + pub commitment_nonce: u128, +} diff --git a/programs/anonbeta1/src/instructions/beacon_bind_callback.rs b/programs/anonbeta1/src/instructions/beacon_bind_callback.rs new file mode 100644 index 0000000..ee8aab0 --- /dev/null +++ b/programs/anonbeta1/src/instructions/beacon_bind_callback.rs @@ -0,0 +1,53 @@ +use anchor_lang::prelude::*; +use arcium_anchor::prelude::*; + +use crate::constants::COMP_DEF_OFFSET_BEACON_BIND; +use crate::errors::ErrorCode; +use crate::events::BeaconBindCompleted; +use crate::{ID, ID_CONST}; + +#[callback_accounts("beacon_bind")] +#[derive(Accounts)] +pub struct BeaconBindCallback<'info> { + pub arcium_program: Program<'info, Arcium>, + + #[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_BEACON_BIND))] + pub comp_def_account: Account<'info, ComputationDefinitionAccount>, + + #[account(address = derive_mxe_pda!())] + pub mxe_account: Account<'info, MXEAccount>, + + /// CHECK: computation_account + pub computation_account: UncheckedAccount<'info>, + + #[account(address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet))] + pub cluster_account: Account<'info, Cluster>, + + #[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)] + /// CHECK: instructions_sysvar + pub instructions_sysvar: AccountInfo<'info>, +} + +pub(crate) fn handler( + ctx: Context, + output: SignedComputationOutputs, +) -> Result<()> { + let _result = match output.verify_output( + &ctx.accounts.cluster_account, + &ctx.accounts.computation_account, + ) { + Ok(BeaconBindOutput { field_0 }) => { + msg!("beacon bind commitment verified"); + emit!(BeaconBindCompleted { + commitment_ciphertext: field_0.ciphertexts[0], + commitment_nonce: field_0.nonce, + }); + field_0 + } + Err(e) => { + msg!("beacon bind verification failed: {}", e); + return Err(ErrorCode::AbortedComputation.into()); + } + }; + Ok(()) +} diff --git a/programs/anonbeta1/src/instructions/heartbeat.rs b/programs/anonbeta1/src/instructions/heartbeat.rs new file mode 100644 index 0000000..1cd0225 --- /dev/null +++ b/programs/anonbeta1/src/instructions/heartbeat.rs @@ -0,0 +1,43 @@ +use anchor_lang::prelude::*; + +use crate::errors::ErrorCode; +use crate::events::BeaconHeartbeat; +use crate::state::BeaconRegistry; + +#[derive(Accounts)] +pub struct Heartbeat<'info> { + pub operator: Signer<'info>, + + #[account( + mut, + seeds = [b"beacon", operator.key().as_ref()], + bump = beacon.bump, + has_one = operator @ ErrorCode::OperatorMismatch, + )] + pub beacon: Account<'info, BeaconRegistry>, +} + +pub(crate) fn handler(ctx: Context) -> Result<()> { + let now = Clock::get()?.unix_timestamp; + let beacon = &mut ctx.accounts.beacon; + + // Clock-skew guard: silently skip the timestamp update if the chain clock + // hasn't moved forward (devnet clocks can be flaky). Still bump the count. + if now > beacon.last_heartbeat { + beacon.last_heartbeat = now; + } + + beacon.heartbeat_count = beacon + .heartbeat_count + .checked_add(1) + .ok_or(ErrorCode::HeartbeatOverflow)?; + + emit!(BeaconHeartbeat { + operator: beacon.operator, + beacon_pda: beacon.key(), + heartbeat_count: beacon.heartbeat_count, + timestamp: beacon.last_heartbeat, + }); + + Ok(()) +} diff --git a/programs/anonbeta1/src/instructions/init_beacon_bind_comp_def.rs b/programs/anonbeta1/src/instructions/init_beacon_bind_comp_def.rs new file mode 100644 index 0000000..e4cbf45 --- /dev/null +++ b/programs/anonbeta1/src/instructions/init_beacon_bind_comp_def.rs @@ -0,0 +1,42 @@ +use anchor_lang::prelude::*; +use arcium_anchor::prelude::*; +use arcium_client::idl::arcium::types::{CircuitSource, OffChainCircuitSource}; +use arcium_macros::circuit_hash; + +use crate::ID; + +#[init_computation_definition_accounts("beacon_bind", payer)] +#[derive(Accounts)] +pub struct InitBeaconBindCompDef<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + #[account(mut, address = derive_mxe_pda!())] + pub mxe_account: Box>, + + #[account(mut)] + /// CHECK: comp_def_account + pub comp_def_account: UncheckedAccount<'info>, + + #[account(mut)] + /// CHECK: address_lookup_table validated by arcium program + pub address_lookup_table: UncheckedAccount<'info>, + + /// CHECK: lut_program + pub lut_program: UncheckedAccount<'info>, + + pub arcium_program: Program<'info, Arcium>, + pub system_program: Program<'info, System>, +} + +pub(crate) fn handler(ctx: Context) -> Result<()> { + init_comp_def( + ctx.accounts, + Some(CircuitSource::OffChain(OffChainCircuitSource { + source: "https://fosjbclmsqobydunswin.supabase.co/storage/v1/object/public/arcium/beacon_bind.arcis".to_string(), + hash: circuit_hash!("beacon_bind"), + })), + None, + )?; + Ok(()) +} diff --git a/programs/anonbeta1/src/instructions/mod.rs b/programs/anonbeta1/src/instructions/mod.rs new file mode 100644 index 0000000..f3a95ef --- /dev/null +++ b/programs/anonbeta1/src/instructions/mod.rs @@ -0,0 +1,5 @@ +pub mod register_beacon; +pub mod heartbeat; +pub mod init_beacon_bind_comp_def; +pub mod register_beacon_private; +pub mod beacon_bind_callback; diff --git a/programs/anonbeta1/src/instructions/register_beacon.rs b/programs/anonbeta1/src/instructions/register_beacon.rs new file mode 100644 index 0000000..dd5fbbe --- /dev/null +++ b/programs/anonbeta1/src/instructions/register_beacon.rs @@ -0,0 +1,62 @@ +use anchor_lang::prelude::*; + +use crate::errors::ErrorCode; +use crate::events::BeaconRegistered; +use crate::state::BeaconRegistry; + +#[derive(Accounts)] +pub struct RegisterBeacon<'info> { + #[account(mut)] + pub operator: Signer<'info>, + + #[account( + init, + payer = operator, + space = 8 + BeaconRegistry::INIT_SPACE, + seeds = [b"beacon", operator.key().as_ref()], + bump, + )] + pub beacon: Account<'info, BeaconRegistry>, + + pub system_program: Program<'info, System>, +} + +pub(crate) fn handler( + ctx: Context, + rns_dest_hash: [u8; 16], + region_code: [u8; 4], +) -> Result<()> { + require!(rns_dest_hash != [0u8; 16], ErrorCode::InvalidRnsHash); + require!( + region_code.iter().all(|b| (0x20..=0x7E).contains(b)), + ErrorCode::InvalidRegionCode + ); + + let now = Clock::get()?.unix_timestamp; + let beacon = &mut ctx.accounts.beacon; + let operator = ctx.accounts.operator.key(); + + beacon.bump = ctx.bumps.beacon; + beacon.operator = operator; + beacon.rns_dest_hash = rns_dest_hash; + beacon.region_code = region_code; + beacon.registered_at = now; + beacon.last_heartbeat = now; + beacon.heartbeat_count = 1; + + emit!(BeaconRegistered { + operator, + beacon_pda: beacon.key(), + rns_dest_hash, + region_code, + registered_at: now, + }); + + msg!( + "beacon registered: operator={} region={:?}", + operator, + region_code + ); + + Ok(()) +} diff --git a/programs/anonbeta1/src/instructions/register_beacon_private.rs b/programs/anonbeta1/src/instructions/register_beacon_private.rs new file mode 100644 index 0000000..41932db --- /dev/null +++ b/programs/anonbeta1/src/instructions/register_beacon_private.rs @@ -0,0 +1,125 @@ +use anchor_lang::prelude::*; +use arcium_anchor::prelude::*; + +use super::beacon_bind_callback::BeaconBindCallback; +use crate::constants::COMP_DEF_OFFSET_BEACON_BIND; +use crate::errors::ErrorCode; +use crate::state::PrivateBeaconRegistry; +use crate::{ArciumSignerAccount, ID, ID_CONST}; + +#[queue_computation_accounts("beacon_bind", payer)] +#[derive(Accounts)] +#[instruction(computation_offset: u64, _binding_id: [u8; 32])] +pub struct RegisterBeaconPrivate<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + init, + payer = payer, + space = 8 + PrivateBeaconRegistry::INIT_SPACE, + seeds = [b"private_beacon", payer.key().as_ref()], + bump, + )] + pub private_beacon: Account<'info, PrivateBeaconRegistry>, + + #[account( + init_if_needed, + space = 9, + payer = payer, + seeds = [b"ArciumSignerAccount"], + bump, + )] + pub sign_pda_account: Account<'info, ArciumSignerAccount>, + + #[account(address = derive_mxe_pda!())] + pub mxe_account: Account<'info, MXEAccount>, + + #[account( + mut, + address = derive_mempool_pda!(mxe_account, ErrorCode::ClusterNotSet) + )] + /// CHECK: mempool_account + pub mempool_account: UncheckedAccount<'info>, + + #[account( + mut, + address = derive_execpool_pda!(mxe_account, ErrorCode::ClusterNotSet) + )] + /// CHECK: executing_pool + pub executing_pool: UncheckedAccount<'info>, + + #[account( + mut, + address = derive_comp_pda!(computation_offset, mxe_account, ErrorCode::ClusterNotSet) + )] + /// CHECK: computation_account + pub computation_account: UncheckedAccount<'info>, + + #[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_BEACON_BIND))] + pub comp_def_account: Box>, + + #[account( + mut, + address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet) + )] + pub cluster_account: Box>, + + #[account(mut, address = ARCIUM_FEE_POOL_ACCOUNT_ADDRESS)] + pub pool_account: Box>, + + #[account(mut, address = ARCIUM_CLOCK_ACCOUNT_ADDRESS)] + pub clock_account: Box>, + + pub system_program: Program<'info, System>, + pub arcium_program: Program<'info, Arcium>, +} + +pub(crate) fn handler( + ctx: Context, + computation_offset: u64, + _binding_id: [u8; 32], + encrypted_rns_dest_hash: [u8; 32], + encrypted_region_code: [u8; 32], + nonce: u128, + pub_key: [u8; 32], +) -> Result<()> { + let args = ArgBuilder::new() + .x25519_pubkey(pub_key) + .plaintext_u128(nonce) + .encrypted_u128(encrypted_rns_dest_hash) + .encrypted_u32(encrypted_region_code) + .build(); + + let now = Clock::get()?.unix_timestamp; + + let private_beacon = &mut ctx.accounts.private_beacon; + private_beacon.bump = ctx.bumps.private_beacon; + private_beacon.operator = ctx.accounts.payer.key(); + private_beacon.x25519_pubkey = pub_key; + private_beacon.verified = false; + private_beacon.last_heartbeat = now; + private_beacon.heartbeat_count = 0; + + ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account; + + queue_computation( + ctx.accounts, + computation_offset, + args, + vec![BeaconBindCallback::callback_ix( + computation_offset, + &ctx.accounts.mxe_account, + &[], + )?], + 1, + 0, + )?; + + msg!( + "beacon bind queued: operator={}", + ctx.accounts.payer.key() + ); + + Ok(()) +} diff --git a/programs/anonbeta1/src/lib.rs b/programs/anonbeta1/src/lib.rs new file mode 100644 index 0000000..7037fda --- /dev/null +++ b/programs/anonbeta1/src/lib.rs @@ -0,0 +1,66 @@ +use anchor_lang::prelude::*; +use arcium_anchor::prelude::*; + +pub mod constants; +pub mod errors; +pub mod events; +pub mod instructions; +pub mod state; + +pub use errors::ErrorCode; +pub use instructions::register_beacon::*; +pub use instructions::heartbeat::*; +pub use instructions::init_beacon_bind_comp_def::*; +pub use instructions::register_beacon_private::*; +pub use instructions::beacon_bind_callback::*; + +declare_id!("anon7uu8UtVoFgS8GCSfw2RqyphJhkN3xEjgPwznYDe"); + +#[arcium_program] +pub mod anonbeta1 { + use super::*; + + pub fn register_beacon( + ctx: Context, + rns_dest_hash: [u8; 16], + region_code: [u8; 4], + ) -> Result<()> { + instructions::register_beacon::handler(ctx, rns_dest_hash, region_code) + } + + pub fn heartbeat(ctx: Context) -> Result<()> { + instructions::heartbeat::handler(ctx) + } + + pub fn init_beacon_bind_comp_def(ctx: Context) -> Result<()> { + instructions::init_beacon_bind_comp_def::handler(ctx) + } + + pub fn register_beacon_private( + ctx: Context, + computation_offset: u64, + binding_id: [u8; 32], + encrypted_rns_dest_hash: [u8; 32], + encrypted_region_code: [u8; 32], + nonce: u128, + pub_key: [u8; 32], + ) -> Result<()> { + instructions::register_beacon_private::handler( + ctx, + computation_offset, + binding_id, + encrypted_rns_dest_hash, + encrypted_region_code, + nonce, + pub_key, + ) + } + + #[arcium_callback(encrypted_ix = "beacon_bind")] + pub fn beacon_bind_callback( + ctx: Context, + output: SignedComputationOutputs, + ) -> Result<()> { + instructions::beacon_bind_callback::handler(ctx, output) + } +} diff --git a/programs/anonbeta1/src/state.rs b/programs/anonbeta1/src/state.rs new file mode 100644 index 0000000..05fe096 --- /dev/null +++ b/programs/anonbeta1/src/state.rs @@ -0,0 +1,35 @@ +use anchor_lang::prelude::*; + +/// On-chain registration for a single anonmesh beacon. +/// +/// PDA seeds: `[b"beacon", operator.key().as_ref()]` +/// One registration per operator wallet. +#[account] +#[derive(InitSpace)] +pub struct BeaconRegistry { + pub bump: u8, + pub operator: Pubkey, + pub rns_dest_hash: [u8; 16], + pub region_code: [u8; 4], + pub registered_at: i64, + pub last_heartbeat: i64, + pub heartbeat_count: u64, +} + +/// Private beacon registration via Arcium MPC. +/// +/// PDA seeds: `[b"private_beacon", operator.key().as_ref()]` +/// Stores the encrypted commitment from beacon_bind circuit. +#[account] +#[derive(InitSpace)] +pub struct PrivateBeaconRegistry { + pub bump: u8, + pub operator: Pubkey, + pub ciphertext: [u8; 32], + pub nonce: u128, + pub x25519_pubkey: [u8; 32], + pub commitment_hash: [u8; 32], + pub verified: bool, + pub last_heartbeat: i64, + pub heartbeat_count: u64, +} diff --git a/yarn.lock b/yarn.lock index 45f1e63..da01736 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,7 +22,7 @@ resolved "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz" integrity sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ== -"@coral-xyz/anchor@^0.32.1", "@coral-xyz/anchor@0.32.1": +"@coral-xyz/anchor@0.32.1", "@coral-xyz/anchor@^0.32.1": version "0.32.1" resolved "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.32.1.tgz" integrity sha512-zAyxFtfeje2FbMA1wzgcdVs7Hng/MijPKpRijoySPCicnvcTQs/+dnPZ/cR+LcXM9v9UYSyW81uRNYZtN5G4yg== @@ -54,16 +54,16 @@ resolved "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz" integrity sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q== -"@esbuild/android-arm@0.27.4": - version "0.27.4" - resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz" - integrity sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ== - "@esbuild/android-arm64@0.27.4": version "0.27.4" resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz" integrity sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw== +"@esbuild/android-arm@0.27.4": + version "0.27.4" + resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz" + integrity sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ== + "@esbuild/android-x64@0.27.4": version "0.27.4" resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz" @@ -89,16 +89,16 @@ resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz" integrity sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ== -"@esbuild/linux-arm@0.27.4": - version "0.27.4" - resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz" - integrity sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg== - "@esbuild/linux-arm64@0.27.4": version "0.27.4" resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz" integrity sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA== +"@esbuild/linux-arm@0.27.4": + version "0.27.4" + resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz" + integrity sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg== + "@esbuild/linux-ia32@0.27.4": version "0.27.4" resolved "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz" @@ -186,7 +186,7 @@ dependencies: "@noble/hashes" "1.8.0" -"@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0", "@noble/hashes@^1.7.1", "@noble/hashes@1.8.0": +"@noble/hashes@1.8.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0", "@noble/hashes@^1.7.1": version "1.8.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== @@ -231,14 +231,6 @@ "@solana/codecs-numbers" "2.0.0-rc.1" "@solana/errors" "2.0.0-rc.1" -"@solana/codecs-numbers@^2.1.0": - version "2.3.0" - resolved "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz" - integrity sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg== - dependencies: - "@solana/codecs-core" "2.3.0" - "@solana/errors" "2.3.0" - "@solana/codecs-numbers@2.0.0-rc.1": version "2.0.0-rc.1" resolved "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz" @@ -247,6 +239,14 @@ "@solana/codecs-core" "2.0.0-rc.1" "@solana/errors" "2.0.0-rc.1" +"@solana/codecs-numbers@^2.1.0": + version "2.3.0" + resolved "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz" + integrity sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg== + dependencies: + "@solana/codecs-core" "2.3.0" + "@solana/errors" "2.3.0" + "@solana/codecs-strings@2.0.0-rc.1": version "2.0.0-rc.1" resolved "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz" @@ -319,7 +319,7 @@ "@solana/spl-token-metadata" "^0.1.6" buffer "^6.0.3" -"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.69.0", "@solana/web3.js@^1.95.3", "@solana/web3.js@^1.95.4", "@solana/web3.js@^1.95.5": +"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.69.0", "@solana/web3.js@^1.95.4": version "1.98.4" resolved "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz" integrity sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw== @@ -551,7 +551,7 @@ buffer-layout@^1.2.0, buffer-layout@^1.2.2: resolved "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz" integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== -buffer@^6.0.3, buffer@~6.0.3, buffer@6.0.3: +buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -559,6 +559,13 @@ buffer@^6.0.3, buffer@~6.0.3, buffer@6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +bufferutil@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.1.0.tgz#a4623541dd23867626bb08a051ec0d2ec0b70294" + integrity sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw== + dependencies: + node-gyp-build "^4.3.0" + camelcase@^6.0.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" @@ -585,12 +592,7 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.3.0: - version "5.6.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz" - integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== - -chalk@^5.4.1: +chalk@^5.3.0, chalk@^5.4.1: version "5.6.2" resolved "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz" integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== @@ -689,16 +691,16 @@ delay@^5.0.0: resolved "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== -diff@^3.1.0: - version "3.5.0" - resolved "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - diff@5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^3.1.0: + version "3.5.0" + resolved "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" @@ -778,11 +780,6 @@ fast-stable-stringify@^1.0.0: resolved "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz" integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== -fastestsmallesttextencoderdecoder@^1.0.22: - version "1.0.22" - resolved "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz" - integrity sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw== - file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" @@ -1007,13 +1004,6 @@ make-error@^1.1.1: resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - minimatch@4.2.1: version "4.2.1" resolved "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz" @@ -1021,6 +1011,13 @@ minimatch@4.2.1: dependencies: brace-expansion "^1.1.7" +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" @@ -1033,7 +1030,7 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" -"mocha@^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X || ^11.X.X", mocha@^9.0.3: +mocha@^9.0.3: version "9.2.2" resolved "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz" integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== @@ -1063,16 +1060,16 @@ mkdirp@^0.5.1: yargs-parser "20.2.4" yargs-unparser "2.0.0" -ms@^2.0.0, ms@2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@2.1.3, ms@^2.0.0: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + nanoid@3.3.1: version "3.3.1" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz" @@ -1085,6 +1082,11 @@ node-fetch@^2.7.0: dependencies: whatwg-url "^5.0.0" +node-gyp-build@^4.3.0: + version "4.8.4" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" + integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -1254,13 +1256,6 @@ superstruct@^2.0.2: resolved "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz" integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - supports-color@8.1.1: version "8.1.1" resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" @@ -1268,6 +1263,13 @@ supports-color@8.1.1: dependencies: has-flag "^4.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + text-encoding-utf-8@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz" @@ -1343,7 +1345,7 @@ type-detect@^4.0.0, type-detect@^4.1.0: resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz" integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== -typescript@^5.7.3, typescript@>=5, typescript@>=5.3.3: +typescript@^5.7.3: version "5.9.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== @@ -1353,6 +1355,13 @@ undici-types@~7.16.0: resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz" integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== +utf-8-validate@^5.0.2: + version "5.0.10" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" + integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== + dependencies: + node-gyp-build "^4.3.0" + uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" @@ -1397,7 +1406,7 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@*, ws@^7.5.10: +ws@^7.5.10: version "7.5.10" resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== @@ -1412,16 +1421,16 @@ y18n@^5.0.5: resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - yargs-parser@20.2.4: version "20.2.4" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" From 68563762b84f98e93f12dd8f4e42240b8b67e49b Mon Sep 17 00:00:00 2001 From: Fawaz Date: Mon, 11 May 2026 20:45:37 -0400 Subject: [PATCH 2/2] Add registration fee to register_beacon_private (SOL + SPL) - 2% fee to treasury on beacon registration (same as ble-revshare) - SOL native: system_program transfer to treasury wallet - SPL token: spl_token transfer via optional mint/token accounts - Added anchor-spl dependency, TREASURY_WALLET + FEE_BPS constants - Upgraded on devnet: anon7uu8UtVoFgS8GCSfw2RqyphJhkN3xEjgPwznYDe --- Cargo.lock | 1 + programs/anonbeta1/Cargo.toml | 3 +- programs/anonbeta1/src/constants.rs | 5 ++ programs/anonbeta1/src/errors.rs | 8 ++ .../instructions/register_beacon_private.rs | 81 ++++++++++++++++++- programs/anonbeta1/src/lib.rs | 2 + 6 files changed, 96 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06d8069..bf5f2b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,7 @@ name = "anonbeta1" version = "0.1.0" dependencies = [ "anchor-lang", + "anchor-spl", "arcium-anchor", "arcium-client", "arcium-macros", diff --git a/programs/anonbeta1/Cargo.toml b/programs/anonbeta1/Cargo.toml index dd6d77b..450d976 100644 --- a/programs/anonbeta1/Cargo.toml +++ b/programs/anonbeta1/Cargo.toml @@ -14,13 +14,14 @@ no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] default = [] -idl-build = ["anchor-lang/idl-build", "arcium-anchor/idl-build"] +idl-build = ["anchor-lang/idl-build", "arcium-anchor/idl-build", "anchor-spl/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] anchor-lang = { version = "0.32.1", features = ["init-if-needed"] } +anchor-spl = { version = "0.32.1", features = ["token"] } arcium-client = { version = "=0.9.3", default-features = false } arcium-macros = { version = "=0.9.3" } arcium-anchor = { version = "=0.9.3" } diff --git a/programs/anonbeta1/src/constants.rs b/programs/anonbeta1/src/constants.rs index f4190fc..c8b631c 100644 --- a/programs/anonbeta1/src/constants.rs +++ b/programs/anonbeta1/src/constants.rs @@ -1,3 +1,8 @@ +use anchor_lang::prelude::*; use arcium_anchor::prelude::comp_def_offset; pub const COMP_DEF_OFFSET_BEACON_BIND: u32 = comp_def_offset("beacon_bind"); + +pub const TREASURY_WALLET: Pubkey = pubkey!("DqyfDvr7yG4d3mtW6AiXgbuVM7GZWqn4RVARFbJxwtFc"); + +pub const FEE_BPS: u64 = 200; // 2% diff --git a/programs/anonbeta1/src/errors.rs b/programs/anonbeta1/src/errors.rs index af3a002..e96b356 100644 --- a/programs/anonbeta1/src/errors.rs +++ b/programs/anonbeta1/src/errors.rs @@ -14,4 +14,12 @@ pub enum ErrorCode { ClusterNotSet, #[msg("Computation was aborted or verification failed")] AbortedComputation, + #[msg("Invalid token account")] + InvalidTokenAccount, + #[msg("Invalid treasury account")] + InvalidTreasury, + #[msg("Math overflow")] + MathOverflow, + #[msg("Registration fee required")] + FeeRequired, } diff --git a/programs/anonbeta1/src/instructions/register_beacon_private.rs b/programs/anonbeta1/src/instructions/register_beacon_private.rs index 41932db..56edfe1 100644 --- a/programs/anonbeta1/src/instructions/register_beacon_private.rs +++ b/programs/anonbeta1/src/instructions/register_beacon_private.rs @@ -1,8 +1,10 @@ use anchor_lang::prelude::*; +use anchor_lang::system_program; +use anchor_spl::token::{self, Token, TokenAccount as SplTokenAccount, Mint as SplMint, Transfer}; use arcium_anchor::prelude::*; use super::beacon_bind_callback::BeaconBindCallback; -use crate::constants::COMP_DEF_OFFSET_BEACON_BIND; +use crate::constants::{COMP_DEF_OFFSET_BEACON_BIND, FEE_BPS, TREASURY_WALLET}; use crate::errors::ErrorCode; use crate::state::PrivateBeaconRegistry; use crate::{ArciumSignerAccount, ID, ID_CONST}; @@ -23,6 +25,28 @@ pub struct RegisterBeaconPrivate<'info> { )] pub private_beacon: Account<'info, PrivateBeaconRegistry>, + // --- Payment accounts (optional SPL, always SOL fallback) --- + + /// Treasury SOL destination. Must match TREASURY_WALLET. + /// CHECK: validated against TREASURY_WALLET constant + #[account(mut, address = TREASURY_WALLET @ ErrorCode::InvalidTreasury)] + pub treasury: UncheckedAccount<'info>, + + /// SPL mint — pass if paying with SPL token (e.g. USDC). Omit for SOL. + pub mint: Option>, + + /// Payer's token account — required if mint is provided. + #[account(mut)] + pub payer_token_account: Option>, + + /// Treasury's token account — required if mint is provided. + #[account(mut)] + pub treasury_token_account: Option>, + + pub token_program: Option>, + + // --- Arcium accounts --- + #[account( init_if_needed, space = 9, @@ -79,11 +103,61 @@ pub(crate) fn handler( ctx: Context, computation_offset: u64, _binding_id: [u8; 32], + amount: u64, encrypted_rns_dest_hash: [u8; 32], encrypted_region_code: [u8; 32], nonce: u128, pub_key: [u8; 32], ) -> Result<()> { + require!(amount > 0, ErrorCode::FeeRequired); + + let fee = amount + .checked_mul(FEE_BPS) + .ok_or(ErrorCode::MathOverflow)? + / 10_000; + + // --- Payment: SPL or SOL --- + if let Some(mint) = &ctx.accounts.mint { + let payer_ata = ctx.accounts.payer_token_account + .as_ref() + .ok_or(ErrorCode::InvalidTokenAccount)?; + let treasury_ata = ctx.accounts.treasury_token_account + .as_ref() + .ok_or(ErrorCode::InvalidTreasury)?; + let token_program = ctx.accounts.token_program + .as_ref() + .ok_or(ErrorCode::InvalidTokenAccount)?; + + require_keys_eq!(payer_ata.owner, ctx.accounts.payer.key(), ErrorCode::InvalidTokenAccount); + require_keys_eq!(payer_ata.mint, mint.key(), ErrorCode::InvalidTokenAccount); + require_keys_eq!(treasury_ata.owner, TREASURY_WALLET, ErrorCode::InvalidTreasury); + require_keys_eq!(treasury_ata.mint, mint.key(), ErrorCode::InvalidTreasury); + + token::transfer( + CpiContext::new( + token_program.to_account_info(), + Transfer { + from: payer_ata.to_account_info(), + to: treasury_ata.to_account_info(), + authority: ctx.accounts.payer.to_account_info(), + }, + ), + fee, + )?; + } else { + system_program::transfer( + CpiContext::new( + ctx.accounts.system_program.to_account_info(), + system_program::Transfer { + from: ctx.accounts.payer.to_account_info(), + to: ctx.accounts.treasury.to_account_info(), + }, + ), + fee, + )?; + } + + // --- Arcium MPC --- let args = ArgBuilder::new() .x25519_pubkey(pub_key) .plaintext_u128(nonce) @@ -117,8 +191,9 @@ pub(crate) fn handler( )?; msg!( - "beacon bind queued: operator={}", - ctx.accounts.payer.key() + "beacon bind queued: operator={} fee={}", + ctx.accounts.payer.key(), + fee ); Ok(()) diff --git a/programs/anonbeta1/src/lib.rs b/programs/anonbeta1/src/lib.rs index 7037fda..4b9aae1 100644 --- a/programs/anonbeta1/src/lib.rs +++ b/programs/anonbeta1/src/lib.rs @@ -40,6 +40,7 @@ pub mod anonbeta1 { ctx: Context, computation_offset: u64, binding_id: [u8; 32], + amount: u64, encrypted_rns_dest_hash: [u8; 32], encrypted_region_code: [u8; 32], nonce: u128, @@ -49,6 +50,7 @@ pub mod anonbeta1 { ctx, computation_offset, binding_id, + amount, encrypted_rns_dest_hash, encrypted_region_code, nonce,