use solana_address::Address;
use solana_instruction::{AccountMeta, Instruction};
use winterwallet_common::{SIGNATURE_LEN, WINTERNITZ_SCALARS};
use winterwallet_core::WinternitzKeypair;
use crate::{
AdvancePlan, Error, WinterWalletAccount, find_wallet_address,
transaction::{DEFAULT_ADVANCE_COMPUTE_UNIT_LIMIT, with_compute_budget},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SigningPosition {
wallet: u32,
parent: u32,
child: u32,
}
impl SigningPosition {
pub const fn new(wallet: u32, parent: u32, child: u32) -> Self {
Self {
wallet,
parent,
child,
}
}
pub fn from_keypair(keypair: &WinternitzKeypair) -> Self {
Self::new(keypair.wallet(), keypair.parent(), keypair.child())
}
pub const fn wallet(&self) -> u32 {
self.wallet
}
pub const fn parent(&self) -> u32 {
self.parent
}
pub const fn child(&self) -> u32 {
self.child
}
pub fn next(&self) -> Result<Self, Error> {
match self.child.checked_add(1) {
Some(child) => Ok(Self::new(self.wallet, self.parent, child)),
None => Ok(Self::new(
self.wallet,
self.parent.checked_add(1).ok_or(Error::PositionOverflow)?,
0,
)),
}
}
fn tuple(&self) -> (u32, u32, u32) {
(self.wallet, self.parent, self.child)
}
fn ensure_can_advance(&self) -> Result<(), Error> {
self.next().map(|_| ())
}
}
pub struct WinterWallet {
id: [u8; 32],
pda: Address,
current_root: [u8; 32],
position: SigningPosition,
}
impl WinterWallet {
pub fn new(id: [u8; 32], current_root: [u8; 32], position: SigningPosition) -> Self {
let (pda, _bump) = find_wallet_address(&id);
Self {
id,
pda,
current_root,
position,
}
}
pub fn from_account(account: &WinterWalletAccount, position: SigningPosition) -> Self {
Self::new(account.id, *account.root.as_bytes(), position)
}
pub fn id(&self) -> &[u8; 32] {
&self.id
}
pub fn pda(&self) -> &Address {
&self.pda
}
pub fn current_root(&self) -> &[u8; 32] {
&self.current_root
}
pub fn position(&self) -> SigningPosition {
self.position
}
pub fn advance_plan(
&self,
new_root: &[u8; 32],
inner_instructions: &[Instruction],
) -> Result<UnsignedAdvance, Error> {
let plan = AdvancePlan::new(&self.pda, new_root, inner_instructions)?;
Ok(UnsignedAdvance {
wallet_id: self.id,
current_root: self.current_root,
position: self.position,
plan,
})
}
pub fn withdraw_plan(
&self,
receiver: &Address,
lamports: u64,
new_root: &[u8; 32],
) -> Result<UnsignedAdvance, Error> {
let plan = AdvancePlan::withdraw(&self.pda, receiver, lamports, new_root)?;
Ok(UnsignedAdvance {
wallet_id: self.id,
current_root: self.current_root,
position: self.position,
plan,
})
}
pub fn close_plan(
&self,
receiver: &Address,
new_root: &[u8; 32],
) -> Result<UnsignedAdvance, Error> {
let plan = AdvancePlan::close(&self.pda, receiver, new_root)?;
Ok(UnsignedAdvance {
wallet_id: self.id,
current_root: self.current_root,
position: self.position,
plan,
})
}
pub fn transfer_plan(
&self,
source_token_account: &Address,
destination_token_account: &Address,
token_program: &Address,
amount: u64,
new_root: &[u8; 32],
) -> Result<UnsignedAdvance, Error> {
self.advance_plan(
new_root,
&[token_transfer(
source_token_account,
destination_token_account,
&self.pda,
amount,
token_program,
)],
)
}
}
pub struct UnsignedAdvance {
wallet_id: [u8; 32],
current_root: [u8; 32],
position: SigningPosition,
plan: AdvancePlan,
}
impl UnsignedAdvance {
pub fn plan(&self) -> &AdvancePlan {
&self.plan
}
pub fn signing_position(&self) -> SigningPosition {
self.position
}
pub fn preimage(&self) -> Vec<&[u8]> {
self.plan.preimage(&self.wallet_id, &self.current_root)
}
pub fn sign(self, keypair: &mut WinternitzKeypair) -> Result<SignedAdvance, Error> {
let actual = SigningPosition::from_keypair(keypair);
if actual != self.position {
return Err(Error::SignerPositionMismatch {
expected: self.position.tuple(),
actual: actual.tuple(),
});
}
actual.ensure_can_advance()?;
let derived_root = keypair
.derive::<WINTERNITZ_SCALARS>()
.to_pubkey()
.merklize();
if derived_root.as_bytes() != &self.current_root {
return Err(Error::RootMismatch);
}
let signature = {
let preimage = self.preimage();
let sig = keypair.sign_and_increment::<WINTERNITZ_SCALARS>(&preimage);
let mut bytes = [0u8; SIGNATURE_LEN];
bytes.copy_from_slice(sig.as_bytes());
bytes
};
let next_position = SigningPosition::from_keypair(keypair);
Ok(SignedAdvance {
wallet_id: self.wallet_id,
signing_position: self.position,
next_position,
signature,
plan: self.plan,
})
}
}
pub struct SignedAdvance {
wallet_id: [u8; 32],
signing_position: SigningPosition,
next_position: SigningPosition,
signature: [u8; SIGNATURE_LEN],
plan: AdvancePlan,
}
impl SignedAdvance {
pub fn wallet_id(&self) -> &[u8; 32] {
&self.wallet_id
}
pub fn wallet_pda(&self) -> &Address {
self.plan.wallet_pda()
}
pub fn signing_position(&self) -> SigningPosition {
self.signing_position
}
pub fn next_position(&self) -> SigningPosition {
self.next_position
}
pub fn signature_bytes(&self) -> &[u8; SIGNATURE_LEN] {
&self.signature
}
pub fn persist<P>(self, persistence: &mut P) -> Result<PersistedAdvance, P::Error>
where
P: AdvancePersistence,
{
persistence.persist_signed_advance(&self)?;
Ok(PersistedAdvance { signed: self })
}
}
pub trait AdvancePersistence {
type Error;
fn persist_signed_advance(&mut self, advance: &SignedAdvance) -> Result<(), Self::Error>;
}
pub struct PersistedAdvance {
signed: SignedAdvance,
}
impl PersistedAdvance {
pub fn signed(&self) -> &SignedAdvance {
&self.signed
}
pub fn advance_instruction(&self) -> Instruction {
self.signed.plan.instruction(&self.signed.signature)
}
pub fn transaction_instructions(
&self,
unit_limit: u32,
unit_price_micro_lamports: u64,
) -> Vec<Instruction> {
with_compute_budget(
&[self.advance_instruction()],
unit_limit,
unit_price_micro_lamports,
)
}
pub fn default_transaction_instructions(
&self,
unit_price_micro_lamports: u64,
) -> Vec<Instruction> {
self.transaction_instructions(
DEFAULT_ADVANCE_COMPUTE_UNIT_LIMIT,
unit_price_micro_lamports,
)
}
pub fn send<S>(&self, sender: &mut S) -> Result<String, S::Error>
where
S: AdvanceSender,
{
sender.send_persisted_advance(self)
}
}
pub trait AdvanceSender {
type Error;
fn send_persisted_advance(&mut self, advance: &PersistedAdvance)
-> Result<String, Self::Error>;
}
pub fn token_transfer(
source: &Address,
destination: &Address,
authority: &Address,
amount: u64,
token_program: &Address,
) -> Instruction {
let mut data = Vec::with_capacity(9);
data.push(3);
data.extend_from_slice(&amount.to_le_bytes());
Instruction {
program_id: *token_program,
accounts: vec![
AccountMeta::new(*source, false),
AccountMeta::new(*destination, false),
AccountMeta::new_readonly(*authority, false),
],
data,
}
}