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

Delegator vote #24

Merged
merged 6 commits into from
Jan 10, 2025
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 CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ file(GLOB_RECURSE LIB_SRC
${CMAKE_CURRENT_SOURCE_DIR}/app/src/c_api/rust.c
${CMAKE_CURRENT_SOURCE_DIR}/app/src/plan/ics20_withdrawal.c
${CMAKE_CURRENT_SOURCE_DIR}/app/src/plan/undelegate_claim.c
${CMAKE_CURRENT_SOURCE_DIR}/app/src/plan/delegator_vote.c
)

add_library(app_lib STATIC ${LIB_SRC})
Expand Down
1 change: 1 addition & 0 deletions app/rust/include/rslib.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ parser_error_t rs_spend_action_hash(spend_plan_t *plan, uint8_t *output, size_t
parser_error_t rs_output_action_hash(output_plan_t *plan, bytes_t *memo_key, uint8_t *output, size_t output_len);
parser_error_t rs_swap_action_hash(swap_plan_t *plan, uint8_t *output, size_t output_len);
parser_error_t rs_undelegate_claim_action_hash(undelegate_claim_plan_t *plan, uint8_t *output, size_t output_len);
parser_error_t rs_delegator_vote_action_hash(delegator_vote_plan_t *plan, uint8_t *output, size_t output_len);
parser_error_t rs_generic_action_hash(bytes_t *data, uint8_t action_type, uint8_t *output, size_t output_len);

parser_error_t rs_get_asset_id_from_metadata(const bytes_t *metadata, uint8_t *asset_id, uint16_t asset_id_len);
Expand Down
1 change: 1 addition & 0 deletions app/rust/src/parser/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub enum ParserError {
ParameterHashError,
EffectHashError,
UndelegateClaimPlanError,
DelegatorVotePlanError,

// Chain related
InvalidChainId,
Expand Down
8 changes: 8 additions & 0 deletions app/rust/src/parser/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct Id(pub Fq);

impl Id {
pub const LEN: usize = ID_LEN_BYTES;
pub const PROTO_LEN: usize = Self::LEN + 4;

/// Compute the value generator for this asset, used for computing balance commitments.
pub fn value_generator(&self) -> decaf377::Element {
Expand All @@ -43,6 +44,13 @@ impl Id {
bytes.copy_from_slice(&self.0.to_bytes());
bytes
}

pub fn to_proto(&self) -> [u8; Self::PROTO_LEN] {
let mut proto = [0u8; Self::PROTO_LEN];
proto[0..4].copy_from_slice(&[0x12, 0x22, 0x0a, 0x20]);
proto[4..].copy_from_slice(&self.to_bytes());
proto
}
}

#[repr(C)]
Expand Down
2 changes: 2 additions & 0 deletions app/rust/src/parser/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ pub struct Note {
#[derive(Clone)]
#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))]
pub struct NoteC {
pub has_value: bool,
pub value: ValueC,
pub rseed: BytesC,
pub has_address: bool,
pub address: AddressC,
}

Expand Down
128 changes: 125 additions & 3 deletions app/rust/src/parser/plans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod output;
pub mod spend;
pub mod swap;
pub mod undelegate_claim;
pub mod delegator_vote;

#[repr(C)]
#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))]
Expand Down Expand Up @@ -139,7 +140,7 @@ pub unsafe extern "C" fn rs_spend_action_hash(
}

let Ok(fvk) = c_fvk_bytes() else {
return ParserError::UnexpectedError as u32;
return ParserError::InvalidFvk as u32;
};
let body_hash_bytes = plan.effect_hash(&fvk);

Expand Down Expand Up @@ -171,7 +172,7 @@ pub unsafe extern "C" fn rs_output_action_hash(
}

let Ok(fvk) = c_fvk_bytes() else {
return ParserError::UnexpectedError as u32;
return ParserError::InvalidFvk as u32;
};

let memo_key_bytes = memo_key.get_bytes().unwrap_or(&[0u8; 32]);
Expand Down Expand Up @@ -205,7 +206,7 @@ pub unsafe extern "C" fn rs_swap_action_hash(
}

let Ok(fvk) = c_fvk_bytes() else {
return ParserError::UnexpectedError as u32;
return ParserError::InvalidFvk as u32;
};

let body_hash_bytes = plan.effect_hash(&fvk);
Expand Down Expand Up @@ -249,6 +250,40 @@ pub unsafe extern "C" fn rs_undelegate_claim_action_hash(
ParserError::Ok as u32
}

#[no_mangle]
/// Use to compute an address and write it back into output
/// argument.
pub unsafe extern "C" fn rs_delegator_vote_action_hash(
plan: &delegator_vote::DelegatorVotePlanC,
output: *mut u8,
output_len: usize,
) -> u32 {
crate::zlog("rs_delegator_vote_action_hash\x00");
let output = std::slice::from_raw_parts_mut(output, output_len);



if output.len() < 64 {
return ParserError::Ok as u32;
}

let Ok(fvk) = c_fvk_bytes() else {
return ParserError::InvalidFvk as u32;
};

let body_hash_bytes = plan.effect_hash(&fvk);

if let Ok(body_hash_bytes) = body_hash_bytes {
let body_hash_array = body_hash_bytes.as_array();
let copy_len: usize = core::cmp::min(output.len(), body_hash_array.len());
output[..copy_len].copy_from_slice(&body_hash_array[..copy_len]);
} else {
return ParserError::DelegatorVotePlanError as u32;
}

ParserError::Ok as u32
}

#[no_mangle]
/// Use to compute an address and write it back into output
/// argument.
Expand Down Expand Up @@ -321,6 +356,7 @@ mod tests {
use crate::parser::value::ValueC;
use crate::parser::penalty::PenaltyC;
use crate::parser::identity_key::IdentityKeyC;

#[test]
fn test_transaction_plan_hash() {
let dummy_action_hashes = ActionsHashC {
Expand Down Expand Up @@ -431,6 +467,8 @@ mod tests {
.unwrap();
let dummy_address_inner = hex::decode("890bc98e3698aa4578e419b028da5672e627c280d8b06166f4c42d5366bccf1fcf3b296cd61e8d744a21f75f2fb697183e18595d8a79008539d8fb138b405db09db65cc42d54c0e772e5d42d5f20b52f").unwrap();
let dummy_note = NoteC {
has_value: true,
has_address: true,
value: dummy_value,
rseed: BytesC::from_slice(&dummy_rseed_bytes),
address: AddressC {
Expand Down Expand Up @@ -727,4 +765,88 @@ mod tests {

}
}

#[test]
fn test_delegator_vote_action_hash() {
// Create dummy ActionC
let dummy_amount = AmountC {
lo: 881370723936900418,
hi: 0,
};

let asset_id_bytes =
hex::decode("29ea9c2f3371f6a487e7e95c247041f4a356f983eb064e5d2b3bcf322ca96a10")
.unwrap();
let dummy_asset_id = IdC {
inner: BytesC::from_slice(&asset_id_bytes),
};

let dummy_value = ValueC {
has_amount: true,
amount: dummy_amount,
has_asset_id: true,
asset_id: dummy_asset_id,
};

let dummy_rseed_bytes =
hex::decode("7c14e7434fde0abeccbc2579e58eeb65045e538b14cad708c988075b9fc0df66")
.unwrap();
let dummy_address_inner = hex::decode("7616f6c402371db1fa79eca16f1892132bbc1ea65e133fa67388049719f62f45c36fe666cc95ecc4444f6561a36d30fa6aad47a89032c8966f05a7cb098f9fd9ee392d0d337f3c35a33284ed4317f392").unwrap();
let dummy_note = NoteC {
has_value: true,
has_address: true,
value: dummy_value,
rseed: BytesC::from_slice(&dummy_rseed_bytes),
address: AddressC {
inner: BytesC::from_slice(&dummy_address_inner),
alt_bech32m: BytesC::default(),
},
};

let dummy_unbonded_amount = AmountC {
lo: 254692294976886837,
hi: 0,
};

let dummy_randomizer_bytes =
hex::decode("8ee3fae74bc73f0107e4f6fbb6a58be4326a0d6991af104f825b8ee4387a6b01")
.unwrap();
let dummy_proof_blinding_r_bytes =
hex::decode("3ad8f590111f2259243cc440cd5aebcce1f96c719095b6d68f6111ceb1f1ae05")
.unwrap();
let dummy_proof_blinding_s_bytes =
hex::decode("f59b1272a4d5ba8bea095233e51b392fe32ec7b97667aa44bf9f89321810ec10")
.unwrap();
let dummy_action = delegator_vote::DelegatorVotePlanC {
proposal: 267193148,
start_position: 20,
has_vote: true,
vote: 1,
has_staked_note: true,
staked_note: dummy_note,
staked_note_position: 30,
has_unbonded_amount: true,
unbonded_amount: dummy_unbonded_amount,
randomizer: BytesC::from_slice(&dummy_randomizer_bytes),
proof_blinding_r: BytesC::from_slice(&dummy_proof_blinding_r_bytes),
proof_blinding_s: BytesC::from_slice(&dummy_proof_blinding_s_bytes),
};

let spend_key = SpendKeyBytes::from([
0xa1, 0xff, 0xba, 0x0c, 0x37, 0x93, 0x1f, 0x0a, 0x62, 0x61, 0x37, 0x52, 0x0d, 0xa6,
0x50, 0x63, 0x2d, 0x35, 0x85, 0x3b, 0xf5, 0x91, 0xb3, 0x6b, 0xb4, 0x28, 0x63, 0x0a,
0x4d, 0x87, 0xc4, 0xdc,
]);
let fvk = spend_key.fvk().unwrap();

let spend_action_hash = dummy_action.effect_hash(&fvk);
let expected_hash = "9b886b91ecbbbe24a6fc01dd425d4954f1e28ddd9bd3c8e446e5f447313c7b3754f7644c1cd6c934fdbfd6a926c16993241ad44ed55b8cf98a0c8f3c2e9ad4ec";
if let Ok(spend_action_hash_bytes) = spend_action_hash {
let computed_hash = hex::encode(spend_action_hash_bytes.as_array());
assert_eq!(computed_hash, expected_hash);
} else {
panic!("spend_action_hash is not Ok");
}
}

}
150 changes: 150 additions & 0 deletions app/rust/src/parser/plans/delegator_vote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*******************************************************************************
* (c) 2024 Zondax GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/

use crate::keys::FullViewingKey;
use crate::parser::{
bytes::BytesC,
amount::{AmountC, Amount},
effect_hash::{create_personalized_state, EffectHash},
note::{NoteC, Note},
nullifier::Nullifier,
value::Value,
};
use decaf377::Fr;
use decaf377_rdsa::{SpendAuth, VerificationKey};
use crate::ParserError;
use crate::utils::protobuf::encode_varint;

pub struct Body {
/// The proposal ID the vote is for.
pub proposal: u64,
/// The start position of the proposal in the TCT.
pub start_position: u64,
/// The vote on the proposal.
pub vote: u8, // With flow encryption, this will be a triple of flow ciphertexts
/// The value of the staked note being used to vote.
pub value: Value, // With flow encryption, this will be a triple of balance commitments, and a public denomination
/// The unbonded amount equivalent to the value above
pub unbonded_amount: Amount,
/// The nullifier of the staked note being used to vote.
pub nullifier: Nullifier,
/// The randomized validating key for the spend authorization signature.
pub rk: VerificationKey<SpendAuth>,
}

#[repr(C)]
#[derive(Clone)]
#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))]
pub struct DelegatorVotePlanC {
pub proposal: u64,
pub start_position: u64,
pub has_vote: bool,
pub vote: u8,
pub has_staked_note: bool,
pub staked_note: NoteC,
pub staked_note_position: u64,
pub has_unbonded_amount: bool,
pub unbonded_amount: AmountC,
pub randomizer: BytesC,
pub proof_blinding_r: BytesC,
pub proof_blinding_s: BytesC,
}

impl DelegatorVotePlanC {
pub fn effect_hash(&self, fvk: &FullViewingKey) -> Result<EffectHash, ParserError> {
let body = self.delegator_vote_body(fvk)?;

let mut state = create_personalized_state("/penumbra.core.component.governance.v1.DelegatorVoteBody");

// proposal
let mut encoded = [0u8; 10];
encoded[0] = 0x08;
let mut pos = 1;
let mut len = encode_varint(body.proposal, &mut encoded[pos..]);
state.update(&encoded[..len + 1]);

// start_position
if body.start_position > 0 {
encoded[0] = 0x10;
pos = 1;
len = encode_varint(body.start_position, &mut encoded[pos..]);
state.update(&encoded[..len + 1]);
}

// vote
state.update(&[0x1a, 0x02]);
encoded[0] = 0x08;
pos = 1;
len = encode_varint(body.vote as u64, &mut encoded[pos..]);
state.update(&encoded[..len + 1]);

// value amount
state.update(&[0x22]);
let (value, value_len) = body.value.to_proto();
state.update(&value[..value_len]);

// unbonded_amount
state.update(&[0x2a]); // encode tag
let (unbonded_amount, unbonded_amount_len) = body.unbonded_amount.to_proto();
state.update(&unbonded_amount[..unbonded_amount_len]);

// nullifier
state.update(&body.nullifier.to_proto());

// rk
state.update(&[0x3a, 0x22, 0x0a, 0x20]);
state.update(&body.rk.to_bytes());

let hash = state.finalize();
Ok(EffectHash(*hash.as_array()))
}

pub fn delegator_vote_body(&self, fvk: &FullViewingKey) -> Result<Body, ParserError> {
let value = Value::try_from(self.staked_note.value.clone())?;
let unbonded_amount = Amount::try_from(self.unbonded_amount.clone())?;

let nk = fvk.nullifier_key();
let note = Note::try_from(self.staked_note.clone())?;
let nullifier = Nullifier::derive(nk, self.staked_note_position, &note.commit()?.0);

let body = Body {
proposal: self.proposal,
start_position: self.start_position,
vote: self.vote,
value,
unbonded_amount,
nullifier,
rk: self.rk(fvk)?,
};

Ok(body)
}

pub fn rk(&self, fvk: &FullViewingKey) -> Result<VerificationKey<SpendAuth>, ParserError> {
Ok(fvk
.spend_verification_key()
.randomize(&self.get_randomizer_fr()?))
}

pub fn get_randomizer(&self) -> Result<&[u8], ParserError> {
self.randomizer.get_bytes()
}

pub fn get_randomizer_fr(&self) -> Result<Fr, ParserError> {
let randomizer_bytes = self.get_randomizer()?;
Ok(Fr::from_le_bytes_mod_order(randomizer_bytes))
}
}
Loading
Loading