Skip to content
Open
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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 3 additions & 1 deletion Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ resolution = true
skip-lint = false

[programs.devnet]
anonbeta1 = "anon7uu8UtVoFgS8GCSfw2RqyphJhkN3xEjgPwznYDe"
ble_revshare = "7xeQNUggKc2e5q6AQxsFBLBkXGg2p54kSx11zVainMks"

[programs.localnet]
anonbeta1 = "anon7uu8UtVoFgS8GCSfw2RqyphJhkN3xEjgPwznYDe"
ble_revshare = "7xeQNUggKc2e5q6AQxsFBLBkXGg2p54kSx11zVainMks"

[registry]
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'"
Expand Down
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions encrypted-ixs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Shared, BindingInput>,
) -> Enc<Shared, u128> {
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)
}
}
110 changes: 110 additions & 0 deletions init-beacon-bind-comp-def.ts
Original file line number Diff line number Diff line change
@@ -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);
});
33 changes: 33 additions & 0 deletions programs/anonbeta1/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[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-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" }
blake3 = "=1.8.2"

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(target_os, values("solana"))',
] }
8 changes: 8 additions & 0 deletions programs/anonbeta1/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +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%
25 changes: 25 additions & 0 deletions programs/anonbeta1/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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,
#[msg("Invalid token account")]
InvalidTokenAccount,
#[msg("Invalid treasury account")]
InvalidTreasury,
#[msg("Math overflow")]
MathOverflow,
#[msg("Registration fee required")]
FeeRequired,
}
24 changes: 24 additions & 0 deletions programs/anonbeta1/src/events.rs
Original file line number Diff line number Diff line change
@@ -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,
}
53 changes: 53 additions & 0 deletions programs/anonbeta1/src/instructions/beacon_bind_callback.rs
Original file line number Diff line number Diff line change
@@ -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<BeaconBindCallback>,
output: SignedComputationOutputs<BeaconBindOutput>,
) -> 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(())
}
43 changes: 43 additions & 0 deletions programs/anonbeta1/src/instructions/heartbeat.rs
Original file line number Diff line number Diff line change
@@ -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<Heartbeat>) -> 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(())
}
Loading