From 53020b0e9b9c52cd84629fd6fd5db0af331a33fc Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 10 Oct 2023 16:06:31 +0100 Subject: [PATCH 01/14] feat(fragment generator): signer reference implementation for generating fragment in bytes format --- Cargo.lock | 31 +++++++++++++++++++ Cargo.toml | 1 + .../src/transaction/transaction.rs | 2 ++ .../src/cryptography/zkps/unit_vector/zkp.rs | 23 ++++++++++++++ .../chain-vote/src/encrypted_vote.rs | 1 + 5 files changed, 58 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 18b6dc9024..2012f93739 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6522,6 +6522,37 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "sign" +version = "0.1.0" +dependencies = [ + "chain-addr", + "chain-core", + "chain-crypto", + "chain-impl-mockchain", + "chain-ser", + "chain-storage", + "chain-time", + "chain-vote", + "clap 4.2.1", + "clap_complete_command", + "color-eyre", + "cryptoxide 0.4.4", + "csv", + "ed25519-dalek", + "hex", + "jormungandr-lib", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.5.1", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "typed-bytes", + "wallet", +] + [[package]] name = "signal-hook" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index 3b066ea2d5..0e4a8c7d89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ members = [ "src/voting-tools-rs", "src/cat-data-service", "src/audit", + "src/sign", ] [workspace.dependencies] diff --git a/src/chain-libs/chain-impl-mockchain/src/transaction/transaction.rs b/src/chain-libs/chain-impl-mockchain/src/transaction/transaction.rs index 6b147493f8..658d3e6f86 100644 --- a/src/chain-libs/chain-impl-mockchain/src/transaction/transaction.rs +++ b/src/chain-libs/chain-impl-mockchain/src/transaction/transaction.rs @@ -265,6 +265,7 @@ pub(super) struct TransactionStruct { /// Verify the structure of the transaction and return all the offsets fn get_spine(slice: &[u8]) -> Result { let sz = slice.len(); + let mut codec = Codec::new(slice); // read payload @@ -316,6 +317,7 @@ fn get_spine(slice: &[u8]) -> Result Vec { + let mut announcements = Vec::new(); + for g in self.ibas.clone() { + announcements.push(g.i); + announcements.push(g.b); + announcements.push(g.a) + } + announcements + } + /// Return an iterator of the encryptions of the polynomial coefficients pub fn ds(&self) -> impl Iterator { self.ds.iter() @@ -303,6 +314,18 @@ impl Zkp { self.zwvs.iter() } + /// Return an iterator of the response related to the randomness + pub fn response_randomness_group_elements(&self) -> Vec { + let mut response = Vec::new(); + for z in self.zwvs.iter().clone() { + response.push(z.z.clone()); + response.push(z.w.clone()); + response.push(z.v.clone()); + } + + response + } + /// Return R pub fn r(&self) -> &Scalar { &self.r diff --git a/src/chain-libs/chain-vote/src/encrypted_vote.rs b/src/chain-libs/chain-vote/src/encrypted_vote.rs index f8a13fda76..79bda44fc6 100644 --- a/src/chain-libs/chain-vote/src/encrypted_vote.rs +++ b/src/chain-libs/chain-vote/src/encrypted_vote.rs @@ -219,6 +219,7 @@ mod tests { #[test] fn unit_vector() { let uv = UnitVector::new(5, 0).unwrap(); + assert_eq!( &uv.iter().collect::>()[..], [true, false, false, false, false] From c973ba8047c27579c67af766173c9ba07ae1d3e9 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 10 Oct 2023 16:06:42 +0100 Subject: [PATCH 02/14] feat(fragment generator): signer reference implementation for generating fragment in bytes format --- src/sign/Cargo.toml | 50 ++++++ src/sign/README.md | 38 +++++ src/sign/src/lib/fragment.rs | 308 +++++++++++++++++++++++++++++++++++ src/sign/src/lib/mod.rs | 1 + src/sign/src/main.rs | 87 ++++++++++ 5 files changed, 484 insertions(+) create mode 100644 src/sign/Cargo.toml create mode 100644 src/sign/README.md create mode 100644 src/sign/src/lib/fragment.rs create mode 100644 src/sign/src/lib/mod.rs create mode 100644 src/sign/src/main.rs diff --git a/src/sign/Cargo.toml b/src/sign/Cargo.toml new file mode 100644 index 0000000000..237f6192e9 --- /dev/null +++ b/src/sign/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "sign" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chain-crypto = { path = "../chain-libs/chain-crypto" } +chain-vote = { path = "../chain-libs/chain-vote" } +jormungandr-lib = { path = "../jormungandr/jormungandr-lib" } +chain-addr = { path = "../chain-libs/chain-addr" } +chain-core = { path = "../chain-libs/chain-core" } +chain-impl-mockchain = { path = "../chain-libs/chain-impl-mockchain" ,features= ["audit"]} +chain-ser = { path = "../chain-libs/chain-ser" } +chain-storage = { path = "../chain-libs/chain-storage" } +chain-time = { path = "../chain-libs/chain-time" } +wallet = { path = "../chain-wallet-libs/wallet" } +typed-bytes = { path = "../chain-libs/typed-bytes" } + +hex = "0.4" +cryptoxide = "0.4.2" +rand_chacha = "0.3" + +clap = { version = "4", features = ["derive", "cargo"] } +clap_complete_command = { version = "0.5" } + +color-eyre = "0.6" +thiserror = "1.0.40" +csv = "1.1" + +serde = "1.0" +serde_json = "1.0" +serde_yaml = "0.8.17" +rand = "0.8.3" + + +rand_core = { version = "0.5.1", default-features = false } + + +ed25519-dalek = "1.0.1" + +[dev-dependencies] +[lib] +name = "lib" +path = "src/lib/mod.rs" + +[[bin]] +name = "signer" +path = "src/main.rs" \ No newline at end of file diff --git a/src/sign/README.md b/src/sign/README.md new file mode 100644 index 0000000000..ae604e3cc1 --- /dev/null +++ b/src/sign/README.md @@ -0,0 +1,38 @@ +# Fragment generator and signer: + +## Specifications + [*see here for format.abnf*](../chain-libs/chain-impl-mockchain/doc/format.abnf) + + [*see here for format.md*](../chain-libs/chain-impl-mockchain/doc/format.md) + +## Ingredients for generating a fragment + +- Election public key +- Alice public key +- Alice private key +- proposal to vote on +- vote plan id (hash of voteplan) + +*Example usage:* + +``` +cargo build --release -p sign +``` + +*Generate fragment in byte representation* + +Secret key: 56e367979579e2ce27fbd305892b0706b7dede999a534a864a7430a5c6aefd3c +Public key: ea084d2d80ed0ab681333d934efc56df3868d13d46a2de3b7f27f40b62e5344d +election public key "bed88887abe0a84f64691fe0bdfa3daf1a6cd697a13f07ae07588910ce39c927" + +```bash + +EK=bed88887abe0a84f64691fe0bdfa3daf1a6cd697a13f07ae07588910ce39c927 +ALICE_SK=56e367979579e2ce27fbd305892b0706b7dede999a534a864a7430a5c6aefd3c +ALICE_PK=ea084d2d80ed0ab681333d934efc56df3868d13d46a2de3b7f27f40b62e5344d +PROPOSAL=5 +VOTE_PLAN_ID=36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b + +./target/release/signer --election-pub-key $EK --private-key $ALICE_SK --public-key $ALICE_PK --proposal $PROPOSAL --vote-plan-id $VOTE_PLAN_ID + +``` \ No newline at end of file diff --git a/src/sign/src/lib/fragment.rs b/src/sign/src/lib/fragment.rs new file mode 100644 index 0000000000..387ff388a9 --- /dev/null +++ b/src/sign/src/lib/fragment.rs @@ -0,0 +1,308 @@ +//! Generate Fragments based upon specification + +use chain_impl_mockchain::certificate::VoteCast; +use chain_ser::packer::Codec; + +use chain_vote::{Ciphertext, ProofOfCorrectVote}; +use ed25519_dalek::{ed25519::signature::Signature, *}; +use std::error; + +/// VoteCast tag +const VOTE_CAST_TAG: u8 = 11; + +/// INPUT-ACCOUNT = %xff VALUE UNTAG-ACCOUNT-ID +const INPUT_ACCOUNT: u8 = 255; + +/// Block epoch + slot +/// This is redundant as time checks have been removed +const EPOCH: u32 = 0; +const SLOT: u32 = 0; + +/// Only 1 input (subsequently 1 witness), no output +/// VoteCast TX should have only 1 input, 0 output and 1 witness (signature). +const INPUT: u8 = 1; +const OUTPUT: u8 = 0; + +/// Nonce +const NONCE: u32 = 0; + +/// Type=2 utxo witness scheme (64 bytes): * ED25519 Signature (64 bytes) +const WITNESS_SCHEME: u8 = 2; + +/// Padding +const PADDING: u8 = 0; + +/// Values in inputs: redundant for voting +const VALUE: u64 = 0; + +/// Padding and Tag are 1 byte each and their size must be added to the fragment size +const PADDING_AND_TAG_SIZE: u32 = 2; + +/// Generate vote fragment +pub fn generate_vote_fragment( + keypair: Keypair, + vote_payload: VoteCast, +) -> Result, Box> { + let mut vote_cast = Codec::new(Vec::new()); + + vote_cast.put_bytes(vote_payload.serialize().as_slice())?; + + let data_to_sign = vote_cast.into_inner().clone(); + + let (inputs, witness) = compose_inputs_and_witnesses(keypair, data_to_sign.clone())?; + + let mut vote_cast = Codec::new(Vec::new()); + vote_cast.put_bytes(&data_to_sign)?; + vote_cast.put_bytes(&inputs)?; + vote_cast.put_bytes(&witness)?; + + let data = vote_cast.into_inner(); + + // prepend msg with size of fragment msg + let mut vote_cast = Codec::new(Vec::new()); + vote_cast.put_be_u32(data.len() as u32 + PADDING_AND_TAG_SIZE)?; + vote_cast.put_u8(PADDING)?; + vote_cast.put_u8(VOTE_CAST_TAG)?; + vote_cast.put_bytes(&data.as_slice())?; + + Ok(vote_cast.into_inner()) +} + +/// Generate Inputs-Outputs-Witnesses +fn compose_inputs_and_witnesses( + keypair: Keypair, + data_to_sign: Vec, +) -> Result<(Vec, Vec), Box> { + let mut inputs = Codec::new(Vec::new()); + + inputs.put_be_u32(EPOCH)?; + inputs.put_be_u32(SLOT)?; + inputs.put_u8(INPUT)?; + inputs.put_u8(OUTPUT)?; + + inputs.put_u8(INPUT_ACCOUNT)?; + inputs.put_be_u64(VALUE)?; + inputs.put_bytes(keypair.public.as_bytes())?; + let inputs = inputs.into_inner().clone(); + + let mut tx_data_to_sign = Codec::new(Vec::new()); + tx_data_to_sign.put_bytes(&data_to_sign.clone())?; + tx_data_to_sign.put_bytes(&inputs.clone())?; + + let signature = keypair.sign(&tx_data_to_sign.into_inner()); + + let mut witness = Codec::new(Vec::new()); + witness.put_u8(WITNESS_SCHEME)?; + witness.put_be_u32(NONCE)?; + witness.put_bytes(signature.as_bytes())?; + let witnesses = witness.into_inner(); + + Ok((inputs, witnesses)) +} + +/// compose encrypted vote part +pub fn compose_encrypted_vote_part( + ciphertexts: Vec, + proof: ProofOfCorrectVote, +) -> Result<(Vec, Vec), Box> { + let mut encrypted_vote = Codec::new(Vec::new()); + + let size_element = ciphertexts.iter().len(); + for cipher in ciphertexts.iter() { + encrypted_vote.put_bytes(&cipher.to_bytes())?; + } + + let encrypted_bytes = encrypted_vote.into_inner(); + + // prepend with SIZE-ELEMENT-8BIT + let mut encrypted_vote = Codec::new(Vec::new()); + encrypted_vote.put_u8(size_element as u8)?; + encrypted_vote.put_bytes(&encrypted_bytes.as_slice())?; + + let mut proof_bytes = Codec::new(Vec::new()); + + for announcement in proof.announcments_group_elements() { + proof_bytes.put_bytes(&announcement.to_bytes())?; + } + + for cipher in proof.ds().into_iter() { + proof_bytes.put_bytes(&cipher.to_bytes())?; + } + + for response in proof.zwvs().into_iter() { + proof_bytes.put_bytes(&response.to_bytes())?; + } + + proof_bytes.put_bytes(&proof.r().as_bytes())?; + + // prepend with SIZE-ELEMENT-8BIT + let mut proof_vote = Codec::new(Vec::new()); + proof_vote.put_u8(proof.len() as u8)?; + proof_vote.put_bytes(proof_bytes.into_inner().as_slice())?; + + let mut payload = Codec::new(Vec::new()); + + payload.put_bytes(&proof_vote.into_inner())?; + + Ok((payload.into_inner(), encrypted_vote.into_inner())) +} + +#[cfg(test)] +mod tests { + + use chain_addr::{AddressReadable, Discrimination}; + use chain_core::property::FromStr; + use chain_impl_mockchain::{ + certificate::{VoteCast, VotePlanId}, + fragment::Fragment, + transaction::InputEnum, + vote::Payload, + }; + use chain_ser::{deser::DeserializeFromSlice, packer::Codec}; + + use ed25519_dalek::Keypair; + use rand_core::OsRng; + + use chain_vote::{ + Crs, ElectionPublicKey, MemberCommunicationKey, MemberState, ProofOfCorrectVote, + }; + + use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; + + use crate::fragment::{compose_encrypted_vote_part, generate_vote_fragment}; + use jormungandr_lib::interfaces::AccountIdentifier; + + #[test] + fn test_fragment_generation() { + let mut csprng = OsRng; + + // User key for signing witness + let keypair = Keypair::generate(&mut csprng); + + let pk = keypair.public.as_bytes().clone(); + + println!("Secret key: {}", hex::encode(keypair.secret.as_bytes())); + println!("Public key: {}", hex::encode(keypair.public.as_bytes())); + + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + + // vote plan id + let vote_plan_id = + "36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b".to_owned(); + + let shared_string = vote_plan_id.to_owned(); + + // election public key + let ek = create_election_pub_key(shared_string, rng.clone()); + + println!("election public key {:?}", hex::encode(ek.to_bytes())); + + // vote + let vote = chain_vote::Vote::new(2, 1 as usize).unwrap(); + + let crs = chain_vote::Crs::from_hash(vote_plan_id.as_bytes()); + + // encrypted vote and proof + let (encrypted_vote, proof) = + chain_impl_mockchain::vote::encrypt_vote(&mut rng, &crs, &ek, vote); + + let payload = Payload::Private { + encrypted_vote, + proof, + }; + + let vp = VotePlanId::from_str(&vote_plan_id).unwrap(); + let vote_cast = VoteCast::new(vp.into(), 5, payload); + + // generate fragment + let fragment_bytes = generate_vote_fragment(keypair, vote_cast).unwrap(); + println!( + "generated fragment: {:?} size:{:?}", + hex::encode(fragment_bytes.clone()), + fragment_bytes.len() + ); + + let fragment = Fragment::deserialize_from_slice(&mut Codec::new(&fragment_bytes)).unwrap(); + + if let Fragment::VoteCast(tx) = fragment.clone() { + let _fragment_id = fragment.hash(); + + let input = tx.as_slice().inputs().iter().next().unwrap().to_enum(); + let caster = if let InputEnum::AccountInput(account_id, _value) = input { + AccountIdentifier::from(account_id).into_address(Discrimination::Production, "ca") + } else { + panic!("unhandled input "); + }; + let certificate = tx.as_slice().payload().into_payload(); + + let voting_key_61824_format = AddressReadable::from_string("ca", &caster.to_string()) + .unwrap() + .to_address(); + + let voting_key = voting_key_61824_format.public_key().unwrap().to_string(); + + assert_eq!(voting_key, hex::encode(pk)); + assert_eq!(certificate.proposal_index(), 5); + assert_eq!(certificate.vote_plan().to_string(), vote_plan_id); + } + } + + fn create_election_pub_key(shared_string: String, mut rng: ChaCha20Rng) -> ElectionPublicKey { + let h = Crs::from_hash(shared_string.as_bytes()); + let mc1 = MemberCommunicationKey::new(&mut rng); + let mc = [mc1.to_public()]; + let threshold = 1; + let m1 = MemberState::new(&mut rng, threshold, &h, &mc, 0); + let participants = vec![m1.public_key()]; + let ek = ElectionPublicKey::from_participants(&participants); + ek + } + + #[test] + fn generate_keys_from_bytes() { + let pk = hex::decode( + "ac247e6cbc2106a8858d67a9b6aa9fc6105a2f42abfd8d269f4096488b7e5d81".to_string(), + ) + .unwrap(); + + let mut sk = hex::decode( + "40cc7f02e04324b63a4db949854d5f24c9041a2bebe9b42064ff868071d1d72d".to_string(), + ) + .unwrap(); + + sk.extend(pk.clone()); + let keys = sk.clone(); + let keypair: Keypair = Keypair::from_bytes(&keys).unwrap(); + + assert_eq!(hex::encode(keypair.public.as_bytes()), hex::encode(pk)); + + println!("Secret key: {}", hex::encode(keypair.secret.as_bytes())); + println!("Public key: {}", hex::encode(keypair.public.as_bytes())); + } + + #[test] + fn test_encrypted_vote_generation() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + + // vote plan id + let vote_plan_id = + "36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b".to_owned(); + + let shared_string = vote_plan_id.to_owned(); + + // election public key + let ek = create_election_pub_key(shared_string, rng.clone()); + + let vote_custom = chain_vote::Vote::new(2, 1 as usize).unwrap(); + let crs = chain_vote::Crs::from_hash(vote_plan_id.as_bytes()); + + let (ciphertexts, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, vote_custom); + let (proof, _enc_vote) = compose_encrypted_vote_part(ciphertexts.clone(), proof).unwrap(); + + let mut msg = Codec::new(proof.as_slice()); + + let p = ProofOfCorrectVote::from_buffer(&mut msg).unwrap(); + + assert_eq!(p.len(), 1); + } +} diff --git a/src/sign/src/lib/mod.rs b/src/sign/src/lib/mod.rs new file mode 100644 index 0000000000..d71fb39d92 --- /dev/null +++ b/src/sign/src/lib/mod.rs @@ -0,0 +1 @@ +pub mod fragment; diff --git a/src/sign/src/main.rs b/src/sign/src/main.rs new file mode 100644 index 0000000000..f95209a4fa --- /dev/null +++ b/src/sign/src/main.rs @@ -0,0 +1,87 @@ +//! +//! Fragment generator +//! + +use chain_core::property::FromStr; +use chain_impl_mockchain::{ + certificate::{VoteCast, VotePlanId}, + vote::Payload, +}; +use chain_vote::ElectionPublicKey; +use clap::Parser; +use color_eyre::Result; +use lib::fragment::generate_vote_fragment; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; + +use ed25519_dalek::*; +use std::error::Error; + +/// +/// Args defines and declares CLI behaviour within the context of clap +/// +#[derive(Parser, Debug, Clone)] +#[clap(about, version, author)] +pub struct Args { + /// Election public key issued by Trent + #[clap(short, long)] + pub election_pub_key: String, + /// Public key of Alice + #[clap(short, long)] + public_key: String, + /// Private key of Alice + #[clap(short, long)] + private_key: String, + /// proposal to vote on + #[clap(short, long)] + proposal: u8, + /// vote plan hash + #[clap(short, long)] + vote_plan_id: String, +} + +fn main() -> Result<(), Box> { + color_eyre::install()?; + + let args = Args::parse(); + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + + let pk = hex::decode(args.public_key)?; + let mut sk = hex::decode(args.private_key)?; + let election_pk = hex::decode(args.election_pub_key)?; + + // join sk+pk together, api requirement + sk.extend(pk.clone()); + let keypair: Keypair = Keypair::from_bytes(&sk)?; + + // vote + let vote = chain_vote::Vote::new(2, 1 as usize).unwrap(); + let crs = chain_vote::Crs::from_hash(args.vote_plan_id.clone().as_bytes()); + + // parse ek key + let ek = ElectionPublicKey::from_bytes(&election_pk) + .ok_or("unable to parse election pub key".to_string())?; + + // encrypted vote and proof + let (encrypted_vote, proof) = + chain_impl_mockchain::vote::encrypt_vote(&mut rng, &crs, &ek, vote); + + // wrap in payload + let payload = Payload::Private { + encrypted_vote, + proof, + }; + + let vote_cast = VoteCast::new( + VotePlanId::from_str(&args.vote_plan_id.clone())?, + args.proposal.clone(), + payload, + ); + + let fragment_bytes = generate_vote_fragment(keypair, vote_cast)?; + + // fragment in hex + println!("{:?}", hex::encode(fragment_bytes.clone())); + + Ok(()) +} From 79a0e6b443a4067dcb2e57a314e31cb5ed0c624b Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 10 Oct 2023 16:08:48 +0100 Subject: [PATCH 03/14] feat(fragment generator): signer reference implementation for generating fragment in bytes format --- .../chain-impl-mockchain/src/transaction/transaction.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/chain-libs/chain-impl-mockchain/src/transaction/transaction.rs b/src/chain-libs/chain-impl-mockchain/src/transaction/transaction.rs index 658d3e6f86..f45e7e7271 100644 --- a/src/chain-libs/chain-impl-mockchain/src/transaction/transaction.rs +++ b/src/chain-libs/chain-impl-mockchain/src/transaction/transaction.rs @@ -317,7 +317,6 @@ fn get_spine(slice: &[u8]) -> Result Date: Tue, 10 Oct 2023 16:13:49 +0100 Subject: [PATCH 04/14] refactor(housekeeping): docs --- src/sign/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sign/src/main.rs b/src/sign/src/main.rs index f95209a4fa..3bc5f189d1 100644 --- a/src/sign/src/main.rs +++ b/src/sign/src/main.rs @@ -80,7 +80,7 @@ fn main() -> Result<(), Box> { let fragment_bytes = generate_vote_fragment(keypair, vote_cast)?; - // fragment in hex + // fragment in hex: output consumed as input to another program println!("{:?}", hex::encode(fragment_bytes.clone())); Ok(()) From 4f762a5c35b587f47be0e7bfddd0f5222cfbf974 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 10 Oct 2023 16:50:19 +0100 Subject: [PATCH 05/14] refactor(housekeeping): docs --- src/sign/README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/sign/README.md b/src/sign/README.md index ae604e3cc1..a7c5c903b9 100644 --- a/src/sign/README.md +++ b/src/sign/README.md @@ -21,18 +21,14 @@ cargo build --release -p sign *Generate fragment in byte representation* -Secret key: 56e367979579e2ce27fbd305892b0706b7dede999a534a864a7430a5c6aefd3c -Public key: ea084d2d80ed0ab681333d934efc56df3868d13d46a2de3b7f27f40b62e5344d -election public key "bed88887abe0a84f64691fe0bdfa3daf1a6cd697a13f07ae07588910ce39c927" - ```bash -EK=bed88887abe0a84f64691fe0bdfa3daf1a6cd697a13f07ae07588910ce39c927 +ELECTION_PUB_KEY=bed88887abe0a84f64691fe0bdfa3daf1a6cd697a13f07ae07588910ce39c927 ALICE_SK=56e367979579e2ce27fbd305892b0706b7dede999a534a864a7430a5c6aefd3c ALICE_PK=ea084d2d80ed0ab681333d934efc56df3868d13d46a2de3b7f27f40b62e5344d PROPOSAL=5 VOTE_PLAN_ID=36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b -./target/release/signer --election-pub-key $EK --private-key $ALICE_SK --public-key $ALICE_PK --proposal $PROPOSAL --vote-plan-id $VOTE_PLAN_ID +./target/release/signer --election-pub-key $ELECTION_PUB_KEY --private-key $ALICE_SK --public-key $ALICE_PK --proposal $PROPOSAL --vote-plan-id $VOTE_PLAN_ID ``` \ No newline at end of file From 7281bfd973e50a8a720777e557c02cba190e29a2 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 10 Oct 2023 16:51:15 +0100 Subject: [PATCH 06/14] refactor(housekeeping): docs --- src/sign/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sign/README.md b/src/sign/README.md index a7c5c903b9..d3e1415a6f 100644 --- a/src/sign/README.md +++ b/src/sign/README.md @@ -19,7 +19,7 @@ cargo build --release -p sign ``` -*Generate fragment in byte representation* +*Generate raw fragment in byte representation* ```bash From bee523fb613a1ea874b1bbbdaac8870afaadfb23 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 10 Oct 2023 23:24:21 +0100 Subject: [PATCH 07/14] feat(encrypted msg byte level encoding): encoding based on specification hand crafted --- src/sign/src/lib/fragment.rs | 79 +++++++++++++++++++++--------------- src/sign/src/main.rs | 28 ++++--------- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/sign/src/lib/fragment.rs b/src/sign/src/lib/fragment.rs index 387ff388a9..4efcf53331 100644 --- a/src/sign/src/lib/fragment.rs +++ b/src/sign/src/lib/fragment.rs @@ -1,12 +1,16 @@ //! Generate Fragments based upon specification +//! Reference specfication for more context in relation to constants outlined in this file. -use chain_impl_mockchain::certificate::VoteCast; use chain_ser::packer::Codec; use chain_vote::{Ciphertext, ProofOfCorrectVote}; use ed25519_dalek::{ed25519::signature::Signature, *}; use std::error; +/// Payload type = 2 +/// %x02 ENCRYPTED-VOTE PROOF-VOTE ; Private payload +const ENCRYPTED_PAYLOAD: u8 = 2; + /// VoteCast tag const VOTE_CAST_TAG: u8 = 11; @@ -26,7 +30,9 @@ const OUTPUT: u8 = 0; /// Nonce const NONCE: u32 = 0; -/// Type=2 utxo witness scheme (64 bytes): * ED25519 Signature (64 bytes) +/// Type = 2 +/// utxo witness scheme +/// ED25519 Signature (64 bytes) const WITNESS_SCHEME: u8 = 2; /// Padding @@ -35,17 +41,24 @@ const PADDING: u8 = 0; /// Values in inputs: redundant for voting const VALUE: u64 = 0; -/// Padding and Tag are 1 byte each and their size must be added to the fragment size +/// Padding and Tag are 1 byte each; size must be added to the fragment size const PADDING_AND_TAG_SIZE: u32 = 2; /// Generate vote fragment pub fn generate_vote_fragment( keypair: Keypair, - vote_payload: VoteCast, + encrypted_vote: Vec, + proof: Vec, + proposal: u8, + vote_plan_id: &[u8], ) -> Result, Box> { let mut vote_cast = Codec::new(Vec::new()); - vote_cast.put_bytes(vote_payload.serialize().as_slice())?; + vote_cast.put_bytes(vote_plan_id)?; + vote_cast.put_u8(proposal)?; + vote_cast.put_u8(ENCRYPTED_PAYLOAD)?; + vote_cast.put_bytes(&encrypted_vote)?; + vote_cast.put_bytes(&proof)?; let data_to_sign = vote_cast.into_inner().clone(); @@ -140,31 +153,26 @@ pub fn compose_encrypted_vote_part( proof_vote.put_u8(proof.len() as u8)?; proof_vote.put_bytes(proof_bytes.into_inner().as_slice())?; - let mut payload = Codec::new(Vec::new()); + let mut proof = Codec::new(Vec::new()); - payload.put_bytes(&proof_vote.into_inner())?; + proof.put_bytes(&proof_vote.into_inner())?; - Ok((payload.into_inner(), encrypted_vote.into_inner())) + Ok((proof.into_inner(), encrypted_vote.into_inner())) } #[cfg(test)] mod tests { use chain_addr::{AddressReadable, Discrimination}; - use chain_core::property::FromStr; - use chain_impl_mockchain::{ - certificate::{VoteCast, VotePlanId}, - fragment::Fragment, - transaction::InputEnum, - vote::Payload, - }; + + use chain_impl_mockchain::{fragment::Fragment, transaction::InputEnum}; use chain_ser::{deser::DeserializeFromSlice, packer::Codec}; use ed25519_dalek::Keypair; use rand_core::OsRng; use chain_vote::{ - Crs, ElectionPublicKey, MemberCommunicationKey, MemberState, ProofOfCorrectVote, + Ciphertext, Crs, ElectionPublicKey, MemberCommunicationKey, MemberState, ProofOfCorrectVote, }; use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; @@ -190,10 +198,8 @@ mod tests { let vote_plan_id = "36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b".to_owned(); - let shared_string = vote_plan_id.to_owned(); - // election public key - let ek = create_election_pub_key(shared_string, rng.clone()); + let ek = create_election_pub_key(vote_plan_id.clone(), rng.clone()); println!("election public key {:?}", hex::encode(ek.to_bytes())); @@ -202,20 +208,20 @@ mod tests { let crs = chain_vote::Crs::from_hash(vote_plan_id.as_bytes()); - // encrypted vote and proof - let (encrypted_vote, proof) = - chain_impl_mockchain::vote::encrypt_vote(&mut rng, &crs, &ek, vote); + let (ciphertexts, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, vote); + let (proof, encrypted_vote) = + compose_encrypted_vote_part(ciphertexts.clone(), proof).unwrap(); - let payload = Payload::Private { + // generate fragment + let fragment_bytes = generate_vote_fragment( + keypair, encrypted_vote, proof, - }; - - let vp = VotePlanId::from_str(&vote_plan_id).unwrap(); - let vote_cast = VoteCast::new(vp.into(), 5, payload); + 5, + &hex::decode(vote_plan_id.clone()).unwrap(), + ) + .unwrap(); - // generate fragment - let fragment_bytes = generate_vote_fragment(keypair, vote_cast).unwrap(); println!( "generated fragment: {:?} size:{:?}", hex::encode(fragment_bytes.clone()), @@ -293,11 +299,20 @@ mod tests { // election public key let ek = create_election_pub_key(shared_string, rng.clone()); - let vote_custom = chain_vote::Vote::new(2, 1 as usize).unwrap(); + let vote = chain_vote::Vote::new(2, 1 as usize).unwrap(); let crs = chain_vote::Crs::from_hash(vote_plan_id.as_bytes()); - let (ciphertexts, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, vote_custom); - let (proof, _enc_vote) = compose_encrypted_vote_part(ciphertexts.clone(), proof).unwrap(); + let (ciphertexts, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, vote); + let (proof, mut enc_vote) = + compose_encrypted_vote_part(ciphertexts.clone(), proof).unwrap(); + + // remove size element, size element is 2 meaning there two ciphertexts + enc_vote.remove(0); + // each ciphertext consists of two 32 byte group elements + let (cipher_a, cipher_b) = enc_vote.split_at(64); + + let _cipher_a = Ciphertext::from_bytes(cipher_a).unwrap(); + let _cipher_b = Ciphertext::from_bytes(cipher_b).unwrap(); let mut msg = Codec::new(proof.as_slice()); diff --git a/src/sign/src/main.rs b/src/sign/src/main.rs index 3bc5f189d1..1cfa1fa44f 100644 --- a/src/sign/src/main.rs +++ b/src/sign/src/main.rs @@ -2,15 +2,10 @@ //! Fragment generator //! -use chain_core::property::FromStr; -use chain_impl_mockchain::{ - certificate::{VoteCast, VotePlanId}, - vote::Payload, -}; use chain_vote::ElectionPublicKey; use clap::Parser; use color_eyre::Result; -use lib::fragment::generate_vote_fragment; +use lib::fragment::{compose_encrypted_vote_part, generate_vote_fragment}; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; @@ -62,23 +57,16 @@ fn main() -> Result<(), Box> { let ek = ElectionPublicKey::from_bytes(&election_pk) .ok_or("unable to parse election pub key".to_string())?; - // encrypted vote and proof - let (encrypted_vote, proof) = - chain_impl_mockchain::vote::encrypt_vote(&mut rng, &crs, &ek, vote); + let (ciphertexts, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, vote); + let (proof, encrypted_vote) = compose_encrypted_vote_part(ciphertexts.clone(), proof)?; - // wrap in payload - let payload = Payload::Private { + let fragment_bytes = generate_vote_fragment( + keypair, encrypted_vote, proof, - }; - - let vote_cast = VoteCast::new( - VotePlanId::from_str(&args.vote_plan_id.clone())?, - args.proposal.clone(), - payload, - ); - - let fragment_bytes = generate_vote_fragment(keypair, vote_cast)?; + args.proposal, + args.vote_plan_id.as_bytes(), + )?; // fragment in hex: output consumed as input to another program println!("{:?}", hex::encode(fragment_bytes.clone())); From 757546f9f569275aa612a1bbfc8a79395905816f Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 11 Oct 2023 11:59:28 +0100 Subject: [PATCH 08/14] refactor(tidy dir structure): housekeeping --- src/sign/Cargo.toml | 11 +- src/sign/src/lib/fragment.rs | 323 ----------------------------------- src/sign/src/lib/mod.rs | 1 - src/sign/src/main.rs | 5 +- 4 files changed, 5 insertions(+), 335 deletions(-) delete mode 100644 src/sign/src/lib/fragment.rs delete mode 100644 src/sign/src/lib/mod.rs diff --git a/src/sign/Cargo.toml b/src/sign/Cargo.toml index 237f6192e9..6728f4d93f 100644 --- a/src/sign/Cargo.toml +++ b/src/sign/Cargo.toml @@ -38,13 +38,4 @@ rand = "0.8.3" rand_core = { version = "0.5.1", default-features = false } -ed25519-dalek = "1.0.1" - -[dev-dependencies] -[lib] -name = "lib" -path = "src/lib/mod.rs" - -[[bin]] -name = "signer" -path = "src/main.rs" \ No newline at end of file +ed25519-dalek = "1.0.1" \ No newline at end of file diff --git a/src/sign/src/lib/fragment.rs b/src/sign/src/lib/fragment.rs deleted file mode 100644 index 4efcf53331..0000000000 --- a/src/sign/src/lib/fragment.rs +++ /dev/null @@ -1,323 +0,0 @@ -//! Generate Fragments based upon specification -//! Reference specfication for more context in relation to constants outlined in this file. - -use chain_ser::packer::Codec; - -use chain_vote::{Ciphertext, ProofOfCorrectVote}; -use ed25519_dalek::{ed25519::signature::Signature, *}; -use std::error; - -/// Payload type = 2 -/// %x02 ENCRYPTED-VOTE PROOF-VOTE ; Private payload -const ENCRYPTED_PAYLOAD: u8 = 2; - -/// VoteCast tag -const VOTE_CAST_TAG: u8 = 11; - -/// INPUT-ACCOUNT = %xff VALUE UNTAG-ACCOUNT-ID -const INPUT_ACCOUNT: u8 = 255; - -/// Block epoch + slot -/// This is redundant as time checks have been removed -const EPOCH: u32 = 0; -const SLOT: u32 = 0; - -/// Only 1 input (subsequently 1 witness), no output -/// VoteCast TX should have only 1 input, 0 output and 1 witness (signature). -const INPUT: u8 = 1; -const OUTPUT: u8 = 0; - -/// Nonce -const NONCE: u32 = 0; - -/// Type = 2 -/// utxo witness scheme -/// ED25519 Signature (64 bytes) -const WITNESS_SCHEME: u8 = 2; - -/// Padding -const PADDING: u8 = 0; - -/// Values in inputs: redundant for voting -const VALUE: u64 = 0; - -/// Padding and Tag are 1 byte each; size must be added to the fragment size -const PADDING_AND_TAG_SIZE: u32 = 2; - -/// Generate vote fragment -pub fn generate_vote_fragment( - keypair: Keypair, - encrypted_vote: Vec, - proof: Vec, - proposal: u8, - vote_plan_id: &[u8], -) -> Result, Box> { - let mut vote_cast = Codec::new(Vec::new()); - - vote_cast.put_bytes(vote_plan_id)?; - vote_cast.put_u8(proposal)?; - vote_cast.put_u8(ENCRYPTED_PAYLOAD)?; - vote_cast.put_bytes(&encrypted_vote)?; - vote_cast.put_bytes(&proof)?; - - let data_to_sign = vote_cast.into_inner().clone(); - - let (inputs, witness) = compose_inputs_and_witnesses(keypair, data_to_sign.clone())?; - - let mut vote_cast = Codec::new(Vec::new()); - vote_cast.put_bytes(&data_to_sign)?; - vote_cast.put_bytes(&inputs)?; - vote_cast.put_bytes(&witness)?; - - let data = vote_cast.into_inner(); - - // prepend msg with size of fragment msg - let mut vote_cast = Codec::new(Vec::new()); - vote_cast.put_be_u32(data.len() as u32 + PADDING_AND_TAG_SIZE)?; - vote_cast.put_u8(PADDING)?; - vote_cast.put_u8(VOTE_CAST_TAG)?; - vote_cast.put_bytes(&data.as_slice())?; - - Ok(vote_cast.into_inner()) -} - -/// Generate Inputs-Outputs-Witnesses -fn compose_inputs_and_witnesses( - keypair: Keypair, - data_to_sign: Vec, -) -> Result<(Vec, Vec), Box> { - let mut inputs = Codec::new(Vec::new()); - - inputs.put_be_u32(EPOCH)?; - inputs.put_be_u32(SLOT)?; - inputs.put_u8(INPUT)?; - inputs.put_u8(OUTPUT)?; - - inputs.put_u8(INPUT_ACCOUNT)?; - inputs.put_be_u64(VALUE)?; - inputs.put_bytes(keypair.public.as_bytes())?; - let inputs = inputs.into_inner().clone(); - - let mut tx_data_to_sign = Codec::new(Vec::new()); - tx_data_to_sign.put_bytes(&data_to_sign.clone())?; - tx_data_to_sign.put_bytes(&inputs.clone())?; - - let signature = keypair.sign(&tx_data_to_sign.into_inner()); - - let mut witness = Codec::new(Vec::new()); - witness.put_u8(WITNESS_SCHEME)?; - witness.put_be_u32(NONCE)?; - witness.put_bytes(signature.as_bytes())?; - let witnesses = witness.into_inner(); - - Ok((inputs, witnesses)) -} - -/// compose encrypted vote part -pub fn compose_encrypted_vote_part( - ciphertexts: Vec, - proof: ProofOfCorrectVote, -) -> Result<(Vec, Vec), Box> { - let mut encrypted_vote = Codec::new(Vec::new()); - - let size_element = ciphertexts.iter().len(); - for cipher in ciphertexts.iter() { - encrypted_vote.put_bytes(&cipher.to_bytes())?; - } - - let encrypted_bytes = encrypted_vote.into_inner(); - - // prepend with SIZE-ELEMENT-8BIT - let mut encrypted_vote = Codec::new(Vec::new()); - encrypted_vote.put_u8(size_element as u8)?; - encrypted_vote.put_bytes(&encrypted_bytes.as_slice())?; - - let mut proof_bytes = Codec::new(Vec::new()); - - for announcement in proof.announcments_group_elements() { - proof_bytes.put_bytes(&announcement.to_bytes())?; - } - - for cipher in proof.ds().into_iter() { - proof_bytes.put_bytes(&cipher.to_bytes())?; - } - - for response in proof.zwvs().into_iter() { - proof_bytes.put_bytes(&response.to_bytes())?; - } - - proof_bytes.put_bytes(&proof.r().as_bytes())?; - - // prepend with SIZE-ELEMENT-8BIT - let mut proof_vote = Codec::new(Vec::new()); - proof_vote.put_u8(proof.len() as u8)?; - proof_vote.put_bytes(proof_bytes.into_inner().as_slice())?; - - let mut proof = Codec::new(Vec::new()); - - proof.put_bytes(&proof_vote.into_inner())?; - - Ok((proof.into_inner(), encrypted_vote.into_inner())) -} - -#[cfg(test)] -mod tests { - - use chain_addr::{AddressReadable, Discrimination}; - - use chain_impl_mockchain::{fragment::Fragment, transaction::InputEnum}; - use chain_ser::{deser::DeserializeFromSlice, packer::Codec}; - - use ed25519_dalek::Keypair; - use rand_core::OsRng; - - use chain_vote::{ - Ciphertext, Crs, ElectionPublicKey, MemberCommunicationKey, MemberState, ProofOfCorrectVote, - }; - - use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; - - use crate::fragment::{compose_encrypted_vote_part, generate_vote_fragment}; - use jormungandr_lib::interfaces::AccountIdentifier; - - #[test] - fn test_fragment_generation() { - let mut csprng = OsRng; - - // User key for signing witness - let keypair = Keypair::generate(&mut csprng); - - let pk = keypair.public.as_bytes().clone(); - - println!("Secret key: {}", hex::encode(keypair.secret.as_bytes())); - println!("Public key: {}", hex::encode(keypair.public.as_bytes())); - - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - - // vote plan id - let vote_plan_id = - "36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b".to_owned(); - - // election public key - let ek = create_election_pub_key(vote_plan_id.clone(), rng.clone()); - - println!("election public key {:?}", hex::encode(ek.to_bytes())); - - // vote - let vote = chain_vote::Vote::new(2, 1 as usize).unwrap(); - - let crs = chain_vote::Crs::from_hash(vote_plan_id.as_bytes()); - - let (ciphertexts, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, vote); - let (proof, encrypted_vote) = - compose_encrypted_vote_part(ciphertexts.clone(), proof).unwrap(); - - // generate fragment - let fragment_bytes = generate_vote_fragment( - keypair, - encrypted_vote, - proof, - 5, - &hex::decode(vote_plan_id.clone()).unwrap(), - ) - .unwrap(); - - println!( - "generated fragment: {:?} size:{:?}", - hex::encode(fragment_bytes.clone()), - fragment_bytes.len() - ); - - let fragment = Fragment::deserialize_from_slice(&mut Codec::new(&fragment_bytes)).unwrap(); - - if let Fragment::VoteCast(tx) = fragment.clone() { - let _fragment_id = fragment.hash(); - - let input = tx.as_slice().inputs().iter().next().unwrap().to_enum(); - let caster = if let InputEnum::AccountInput(account_id, _value) = input { - AccountIdentifier::from(account_id).into_address(Discrimination::Production, "ca") - } else { - panic!("unhandled input "); - }; - let certificate = tx.as_slice().payload().into_payload(); - - let voting_key_61824_format = AddressReadable::from_string("ca", &caster.to_string()) - .unwrap() - .to_address(); - - let voting_key = voting_key_61824_format.public_key().unwrap().to_string(); - - assert_eq!(voting_key, hex::encode(pk)); - assert_eq!(certificate.proposal_index(), 5); - assert_eq!(certificate.vote_plan().to_string(), vote_plan_id); - } - } - - fn create_election_pub_key(shared_string: String, mut rng: ChaCha20Rng) -> ElectionPublicKey { - let h = Crs::from_hash(shared_string.as_bytes()); - let mc1 = MemberCommunicationKey::new(&mut rng); - let mc = [mc1.to_public()]; - let threshold = 1; - let m1 = MemberState::new(&mut rng, threshold, &h, &mc, 0); - let participants = vec![m1.public_key()]; - let ek = ElectionPublicKey::from_participants(&participants); - ek - } - - #[test] - fn generate_keys_from_bytes() { - let pk = hex::decode( - "ac247e6cbc2106a8858d67a9b6aa9fc6105a2f42abfd8d269f4096488b7e5d81".to_string(), - ) - .unwrap(); - - let mut sk = hex::decode( - "40cc7f02e04324b63a4db949854d5f24c9041a2bebe9b42064ff868071d1d72d".to_string(), - ) - .unwrap(); - - sk.extend(pk.clone()); - let keys = sk.clone(); - let keypair: Keypair = Keypair::from_bytes(&keys).unwrap(); - - assert_eq!(hex::encode(keypair.public.as_bytes()), hex::encode(pk)); - - println!("Secret key: {}", hex::encode(keypair.secret.as_bytes())); - println!("Public key: {}", hex::encode(keypair.public.as_bytes())); - } - - #[test] - fn test_encrypted_vote_generation() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - - // vote plan id - let vote_plan_id = - "36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b".to_owned(); - - let shared_string = vote_plan_id.to_owned(); - - // election public key - let ek = create_election_pub_key(shared_string, rng.clone()); - - let vote = chain_vote::Vote::new(2, 1 as usize).unwrap(); - let crs = chain_vote::Crs::from_hash(vote_plan_id.as_bytes()); - - let (ciphertexts, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, vote); - let (proof, mut enc_vote) = - compose_encrypted_vote_part(ciphertexts.clone(), proof).unwrap(); - - // remove size element, size element is 2 meaning there two ciphertexts - enc_vote.remove(0); - // each ciphertext consists of two 32 byte group elements - let (cipher_a, cipher_b) = enc_vote.split_at(64); - - let _cipher_a = Ciphertext::from_bytes(cipher_a).unwrap(); - let _cipher_b = Ciphertext::from_bytes(cipher_b).unwrap(); - - let mut msg = Codec::new(proof.as_slice()); - - let p = ProofOfCorrectVote::from_buffer(&mut msg).unwrap(); - - assert_eq!(p.len(), 1); - } -} diff --git a/src/sign/src/lib/mod.rs b/src/sign/src/lib/mod.rs deleted file mode 100644 index d71fb39d92..0000000000 --- a/src/sign/src/lib/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod fragment; diff --git a/src/sign/src/main.rs b/src/sign/src/main.rs index 1cfa1fa44f..dd3c477240 100644 --- a/src/sign/src/main.rs +++ b/src/sign/src/main.rs @@ -5,13 +5,16 @@ use chain_vote::ElectionPublicKey; use clap::Parser; use color_eyre::Result; -use lib::fragment::{compose_encrypted_vote_part, generate_vote_fragment}; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; use ed25519_dalek::*; use std::error::Error; +use crate::fragment::{compose_encrypted_vote_part, generate_vote_fragment}; + +mod fragment; + /// /// Args defines and declares CLI behaviour within the context of clap /// From 72f0fcf0a79b6fa3e4ebd99b20530fe68f9a4bc8 Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 11 Oct 2023 11:59:54 +0100 Subject: [PATCH 09/14] refactor(tidy dir structure): housekeeping --- src/sign/src/fragment.rs | 323 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 src/sign/src/fragment.rs diff --git a/src/sign/src/fragment.rs b/src/sign/src/fragment.rs new file mode 100644 index 0000000000..4efcf53331 --- /dev/null +++ b/src/sign/src/fragment.rs @@ -0,0 +1,323 @@ +//! Generate Fragments based upon specification +//! Reference specfication for more context in relation to constants outlined in this file. + +use chain_ser::packer::Codec; + +use chain_vote::{Ciphertext, ProofOfCorrectVote}; +use ed25519_dalek::{ed25519::signature::Signature, *}; +use std::error; + +/// Payload type = 2 +/// %x02 ENCRYPTED-VOTE PROOF-VOTE ; Private payload +const ENCRYPTED_PAYLOAD: u8 = 2; + +/// VoteCast tag +const VOTE_CAST_TAG: u8 = 11; + +/// INPUT-ACCOUNT = %xff VALUE UNTAG-ACCOUNT-ID +const INPUT_ACCOUNT: u8 = 255; + +/// Block epoch + slot +/// This is redundant as time checks have been removed +const EPOCH: u32 = 0; +const SLOT: u32 = 0; + +/// Only 1 input (subsequently 1 witness), no output +/// VoteCast TX should have only 1 input, 0 output and 1 witness (signature). +const INPUT: u8 = 1; +const OUTPUT: u8 = 0; + +/// Nonce +const NONCE: u32 = 0; + +/// Type = 2 +/// utxo witness scheme +/// ED25519 Signature (64 bytes) +const WITNESS_SCHEME: u8 = 2; + +/// Padding +const PADDING: u8 = 0; + +/// Values in inputs: redundant for voting +const VALUE: u64 = 0; + +/// Padding and Tag are 1 byte each; size must be added to the fragment size +const PADDING_AND_TAG_SIZE: u32 = 2; + +/// Generate vote fragment +pub fn generate_vote_fragment( + keypair: Keypair, + encrypted_vote: Vec, + proof: Vec, + proposal: u8, + vote_plan_id: &[u8], +) -> Result, Box> { + let mut vote_cast = Codec::new(Vec::new()); + + vote_cast.put_bytes(vote_plan_id)?; + vote_cast.put_u8(proposal)?; + vote_cast.put_u8(ENCRYPTED_PAYLOAD)?; + vote_cast.put_bytes(&encrypted_vote)?; + vote_cast.put_bytes(&proof)?; + + let data_to_sign = vote_cast.into_inner().clone(); + + let (inputs, witness) = compose_inputs_and_witnesses(keypair, data_to_sign.clone())?; + + let mut vote_cast = Codec::new(Vec::new()); + vote_cast.put_bytes(&data_to_sign)?; + vote_cast.put_bytes(&inputs)?; + vote_cast.put_bytes(&witness)?; + + let data = vote_cast.into_inner(); + + // prepend msg with size of fragment msg + let mut vote_cast = Codec::new(Vec::new()); + vote_cast.put_be_u32(data.len() as u32 + PADDING_AND_TAG_SIZE)?; + vote_cast.put_u8(PADDING)?; + vote_cast.put_u8(VOTE_CAST_TAG)?; + vote_cast.put_bytes(&data.as_slice())?; + + Ok(vote_cast.into_inner()) +} + +/// Generate Inputs-Outputs-Witnesses +fn compose_inputs_and_witnesses( + keypair: Keypair, + data_to_sign: Vec, +) -> Result<(Vec, Vec), Box> { + let mut inputs = Codec::new(Vec::new()); + + inputs.put_be_u32(EPOCH)?; + inputs.put_be_u32(SLOT)?; + inputs.put_u8(INPUT)?; + inputs.put_u8(OUTPUT)?; + + inputs.put_u8(INPUT_ACCOUNT)?; + inputs.put_be_u64(VALUE)?; + inputs.put_bytes(keypair.public.as_bytes())?; + let inputs = inputs.into_inner().clone(); + + let mut tx_data_to_sign = Codec::new(Vec::new()); + tx_data_to_sign.put_bytes(&data_to_sign.clone())?; + tx_data_to_sign.put_bytes(&inputs.clone())?; + + let signature = keypair.sign(&tx_data_to_sign.into_inner()); + + let mut witness = Codec::new(Vec::new()); + witness.put_u8(WITNESS_SCHEME)?; + witness.put_be_u32(NONCE)?; + witness.put_bytes(signature.as_bytes())?; + let witnesses = witness.into_inner(); + + Ok((inputs, witnesses)) +} + +/// compose encrypted vote part +pub fn compose_encrypted_vote_part( + ciphertexts: Vec, + proof: ProofOfCorrectVote, +) -> Result<(Vec, Vec), Box> { + let mut encrypted_vote = Codec::new(Vec::new()); + + let size_element = ciphertexts.iter().len(); + for cipher in ciphertexts.iter() { + encrypted_vote.put_bytes(&cipher.to_bytes())?; + } + + let encrypted_bytes = encrypted_vote.into_inner(); + + // prepend with SIZE-ELEMENT-8BIT + let mut encrypted_vote = Codec::new(Vec::new()); + encrypted_vote.put_u8(size_element as u8)?; + encrypted_vote.put_bytes(&encrypted_bytes.as_slice())?; + + let mut proof_bytes = Codec::new(Vec::new()); + + for announcement in proof.announcments_group_elements() { + proof_bytes.put_bytes(&announcement.to_bytes())?; + } + + for cipher in proof.ds().into_iter() { + proof_bytes.put_bytes(&cipher.to_bytes())?; + } + + for response in proof.zwvs().into_iter() { + proof_bytes.put_bytes(&response.to_bytes())?; + } + + proof_bytes.put_bytes(&proof.r().as_bytes())?; + + // prepend with SIZE-ELEMENT-8BIT + let mut proof_vote = Codec::new(Vec::new()); + proof_vote.put_u8(proof.len() as u8)?; + proof_vote.put_bytes(proof_bytes.into_inner().as_slice())?; + + let mut proof = Codec::new(Vec::new()); + + proof.put_bytes(&proof_vote.into_inner())?; + + Ok((proof.into_inner(), encrypted_vote.into_inner())) +} + +#[cfg(test)] +mod tests { + + use chain_addr::{AddressReadable, Discrimination}; + + use chain_impl_mockchain::{fragment::Fragment, transaction::InputEnum}; + use chain_ser::{deser::DeserializeFromSlice, packer::Codec}; + + use ed25519_dalek::Keypair; + use rand_core::OsRng; + + use chain_vote::{ + Ciphertext, Crs, ElectionPublicKey, MemberCommunicationKey, MemberState, ProofOfCorrectVote, + }; + + use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; + + use crate::fragment::{compose_encrypted_vote_part, generate_vote_fragment}; + use jormungandr_lib::interfaces::AccountIdentifier; + + #[test] + fn test_fragment_generation() { + let mut csprng = OsRng; + + // User key for signing witness + let keypair = Keypair::generate(&mut csprng); + + let pk = keypair.public.as_bytes().clone(); + + println!("Secret key: {}", hex::encode(keypair.secret.as_bytes())); + println!("Public key: {}", hex::encode(keypair.public.as_bytes())); + + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + + // vote plan id + let vote_plan_id = + "36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b".to_owned(); + + // election public key + let ek = create_election_pub_key(vote_plan_id.clone(), rng.clone()); + + println!("election public key {:?}", hex::encode(ek.to_bytes())); + + // vote + let vote = chain_vote::Vote::new(2, 1 as usize).unwrap(); + + let crs = chain_vote::Crs::from_hash(vote_plan_id.as_bytes()); + + let (ciphertexts, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, vote); + let (proof, encrypted_vote) = + compose_encrypted_vote_part(ciphertexts.clone(), proof).unwrap(); + + // generate fragment + let fragment_bytes = generate_vote_fragment( + keypair, + encrypted_vote, + proof, + 5, + &hex::decode(vote_plan_id.clone()).unwrap(), + ) + .unwrap(); + + println!( + "generated fragment: {:?} size:{:?}", + hex::encode(fragment_bytes.clone()), + fragment_bytes.len() + ); + + let fragment = Fragment::deserialize_from_slice(&mut Codec::new(&fragment_bytes)).unwrap(); + + if let Fragment::VoteCast(tx) = fragment.clone() { + let _fragment_id = fragment.hash(); + + let input = tx.as_slice().inputs().iter().next().unwrap().to_enum(); + let caster = if let InputEnum::AccountInput(account_id, _value) = input { + AccountIdentifier::from(account_id).into_address(Discrimination::Production, "ca") + } else { + panic!("unhandled input "); + }; + let certificate = tx.as_slice().payload().into_payload(); + + let voting_key_61824_format = AddressReadable::from_string("ca", &caster.to_string()) + .unwrap() + .to_address(); + + let voting_key = voting_key_61824_format.public_key().unwrap().to_string(); + + assert_eq!(voting_key, hex::encode(pk)); + assert_eq!(certificate.proposal_index(), 5); + assert_eq!(certificate.vote_plan().to_string(), vote_plan_id); + } + } + + fn create_election_pub_key(shared_string: String, mut rng: ChaCha20Rng) -> ElectionPublicKey { + let h = Crs::from_hash(shared_string.as_bytes()); + let mc1 = MemberCommunicationKey::new(&mut rng); + let mc = [mc1.to_public()]; + let threshold = 1; + let m1 = MemberState::new(&mut rng, threshold, &h, &mc, 0); + let participants = vec![m1.public_key()]; + let ek = ElectionPublicKey::from_participants(&participants); + ek + } + + #[test] + fn generate_keys_from_bytes() { + let pk = hex::decode( + "ac247e6cbc2106a8858d67a9b6aa9fc6105a2f42abfd8d269f4096488b7e5d81".to_string(), + ) + .unwrap(); + + let mut sk = hex::decode( + "40cc7f02e04324b63a4db949854d5f24c9041a2bebe9b42064ff868071d1d72d".to_string(), + ) + .unwrap(); + + sk.extend(pk.clone()); + let keys = sk.clone(); + let keypair: Keypair = Keypair::from_bytes(&keys).unwrap(); + + assert_eq!(hex::encode(keypair.public.as_bytes()), hex::encode(pk)); + + println!("Secret key: {}", hex::encode(keypair.secret.as_bytes())); + println!("Public key: {}", hex::encode(keypair.public.as_bytes())); + } + + #[test] + fn test_encrypted_vote_generation() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + + // vote plan id + let vote_plan_id = + "36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b".to_owned(); + + let shared_string = vote_plan_id.to_owned(); + + // election public key + let ek = create_election_pub_key(shared_string, rng.clone()); + + let vote = chain_vote::Vote::new(2, 1 as usize).unwrap(); + let crs = chain_vote::Crs::from_hash(vote_plan_id.as_bytes()); + + let (ciphertexts, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, vote); + let (proof, mut enc_vote) = + compose_encrypted_vote_part(ciphertexts.clone(), proof).unwrap(); + + // remove size element, size element is 2 meaning there two ciphertexts + enc_vote.remove(0); + // each ciphertext consists of two 32 byte group elements + let (cipher_a, cipher_b) = enc_vote.split_at(64); + + let _cipher_a = Ciphertext::from_bytes(cipher_a).unwrap(); + let _cipher_b = Ciphertext::from_bytes(cipher_b).unwrap(); + + let mut msg = Codec::new(proof.as_slice()); + + let p = ProofOfCorrectVote::from_buffer(&mut msg).unwrap(); + + assert_eq!(p.len(), 1); + } +} From b6043c63a4069359c21444687fe949424b94e747 Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 11 Oct 2023 12:02:21 +0100 Subject: [PATCH 10/14] refactor(tidy dir structure): housekeeping --- src/sign/src/fragment.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sign/src/fragment.rs b/src/sign/src/fragment.rs index 4efcf53331..4265d49cc6 100644 --- a/src/sign/src/fragment.rs +++ b/src/sign/src/fragment.rs @@ -44,7 +44,7 @@ const VALUE: u64 = 0; /// Padding and Tag are 1 byte each; size must be added to the fragment size const PADDING_AND_TAG_SIZE: u32 = 2; -/// Generate vote fragment +/// Generate vote fragment in bytes pub fn generate_vote_fragment( keypair: Keypair, encrypted_vote: Vec, @@ -81,7 +81,7 @@ pub fn generate_vote_fragment( Ok(vote_cast.into_inner()) } -/// Generate Inputs-Outputs-Witnesses +/// Generate Inputs-Outputs-Witnesses in bytes fn compose_inputs_and_witnesses( keypair: Keypair, data_to_sign: Vec, @@ -113,7 +113,7 @@ fn compose_inputs_and_witnesses( Ok((inputs, witnesses)) } -/// compose encrypted vote part +/// compose encrypted vote and proof in bytes pub fn compose_encrypted_vote_part( ciphertexts: Vec, proof: ProofOfCorrectVote, From 34c0b9c4d9bc0c4277ec3f47e797e010ff5894e3 Mon Sep 17 00:00:00 2001 From: cong-or Date: Thu, 12 Oct 2023 22:14:47 +0100 Subject: [PATCH 11/14] refactor(bech32 decoding): election pub key --- Cargo.lock | 1 + src/sign/Cargo.toml | 1 + src/sign/README.md | 4 ++-- src/sign/src/fragment.rs | 10 +++++----- src/sign/src/main.rs | 12 ++++++++++-- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2012f93739..b85ebd47a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6526,6 +6526,7 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" name = "sign" version = "0.1.0" dependencies = [ + "bech32 0.8.1", "chain-addr", "chain-core", "chain-crypto", diff --git a/src/sign/Cargo.toml b/src/sign/Cargo.toml index 6728f4d93f..bd8060caa1 100644 --- a/src/sign/Cargo.toml +++ b/src/sign/Cargo.toml @@ -33,6 +33,7 @@ serde = "1.0" serde_json = "1.0" serde_yaml = "0.8.17" rand = "0.8.3" +bech32 = "0.8" rand_core = { version = "0.5.1", default-features = false } diff --git a/src/sign/README.md b/src/sign/README.md index d3e1415a6f..199360735a 100644 --- a/src/sign/README.md +++ b/src/sign/README.md @@ -23,12 +23,12 @@ cargo build --release -p sign ```bash -ELECTION_PUB_KEY=bed88887abe0a84f64691fe0bdfa3daf1a6cd697a13f07ae07588910ce39c927 +ELECTION_PUB_KEY=ristretto255_votepk1ppxnuxrqa4728evnp2ues000uvwvwtxmtf77ejc29lknjuqqu44s4cfmja ALICE_SK=56e367979579e2ce27fbd305892b0706b7dede999a534a864a7430a5c6aefd3c ALICE_PK=ea084d2d80ed0ab681333d934efc56df3868d13d46a2de3b7f27f40b62e5344d PROPOSAL=5 VOTE_PLAN_ID=36ad42885189a0ac3438cdb57bc8ac7f6542e05a59d1f2e4d1d38194c9d4ac7b -./target/release/signer --election-pub-key $ELECTION_PUB_KEY --private-key $ALICE_SK --public-key $ALICE_PK --proposal $PROPOSAL --vote-plan-id $VOTE_PLAN_ID +./target/release/sign --election-pub-key $ELECTION_PUB_KEY --private-key $ALICE_SK --public-key $ALICE_PK --proposal $PROPOSAL --vote-plan-id $VOTE_PLAN_ID ``` \ No newline at end of file diff --git a/src/sign/src/fragment.rs b/src/sign/src/fragment.rs index 4265d49cc6..1baf477814 100644 --- a/src/sign/src/fragment.rs +++ b/src/sign/src/fragment.rs @@ -76,7 +76,7 @@ pub fn generate_vote_fragment( vote_cast.put_be_u32(data.len() as u32 + PADDING_AND_TAG_SIZE)?; vote_cast.put_u8(PADDING)?; vote_cast.put_u8(VOTE_CAST_TAG)?; - vote_cast.put_bytes(&data.as_slice())?; + vote_cast.put_bytes(data.as_slice())?; Ok(vote_cast.into_inner()) } @@ -130,7 +130,7 @@ pub fn compose_encrypted_vote_part( // prepend with SIZE-ELEMENT-8BIT let mut encrypted_vote = Codec::new(Vec::new()); encrypted_vote.put_u8(size_element as u8)?; - encrypted_vote.put_bytes(&encrypted_bytes.as_slice())?; + encrypted_vote.put_bytes(encrypted_bytes.as_slice())?; let mut proof_bytes = Codec::new(Vec::new()); @@ -138,15 +138,15 @@ pub fn compose_encrypted_vote_part( proof_bytes.put_bytes(&announcement.to_bytes())?; } - for cipher in proof.ds().into_iter() { + for cipher in proof.ds() { proof_bytes.put_bytes(&cipher.to_bytes())?; } - for response in proof.zwvs().into_iter() { + for response in proof.zwvs() { proof_bytes.put_bytes(&response.to_bytes())?; } - proof_bytes.put_bytes(&proof.r().as_bytes())?; + proof_bytes.put_bytes(proof.r().as_bytes())?; // prepend with SIZE-ELEMENT-8BIT let mut proof_vote = Codec::new(Vec::new()); diff --git a/src/sign/src/main.rs b/src/sign/src/main.rs index dd3c477240..781e495f6e 100644 --- a/src/sign/src/main.rs +++ b/src/sign/src/main.rs @@ -2,6 +2,8 @@ //! Fragment generator //! +use bech32::Error as Bech32Error; +use bech32::FromBase32; use chain_vote::ElectionPublicKey; use clap::Parser; use color_eyre::Result; @@ -46,14 +48,20 @@ fn main() -> Result<(), Box> { let pk = hex::decode(args.public_key)?; let mut sk = hex::decode(args.private_key)?; - let election_pk = hex::decode(args.election_pub_key)?; + + // Election pub key published as a Bech32_encoded address + // which consists of 3 parts: A Human-Readable Part (HRP) + Separator + Data: + let (_hrp, data, _variant) = + bech32::decode(&args.election_pub_key).map_err(Bech32Error::from)?; + + let election_pk = Vec::::from_base32(&data).map_err(Bech32Error::from)?; // join sk+pk together, api requirement sk.extend(pk.clone()); let keypair: Keypair = Keypair::from_bytes(&sk)?; // vote - let vote = chain_vote::Vote::new(2, 1 as usize).unwrap(); + let vote = chain_vote::Vote::new(2, 1_usize)?; let crs = chain_vote::Crs::from_hash(args.vote_plan_id.clone().as_bytes()); // parse ek key From 124483d6e01d8f3271b9399014321299a5df6fe4 Mon Sep 17 00:00:00 2001 From: cong-or Date: Thu, 12 Oct 2023 22:48:41 +0100 Subject: [PATCH 12/14] fix(cargo lock): --locked --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b2cf8c297f..76084ece37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6766,7 +6766,7 @@ dependencies = [ "chain-storage", "chain-time", "chain-vote", - "clap 4.2.1", + "clap 4.4.6", "clap_complete_command", "color-eyre", "cryptoxide 0.4.4", From 013655500628a259a35831ae8815ed80d17e3527 Mon Sep 17 00:00:00 2001 From: cong-or Date: Thu, 12 Oct 2023 23:14:09 +0100 Subject: [PATCH 13/14] fix(cargo lock): --locked --- src/sign/src/fragment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sign/src/fragment.rs b/src/sign/src/fragment.rs index 1baf477814..b50d928ebe 100644 --- a/src/sign/src/fragment.rs +++ b/src/sign/src/fragment.rs @@ -146,7 +146,7 @@ pub fn compose_encrypted_vote_part( proof_bytes.put_bytes(&response.to_bytes())?; } - proof_bytes.put_bytes(proof.r().as_bytes())?; + proof_bytes.put_bytes(&proof.r().to_bytes())?; // prepend with SIZE-ELEMENT-8BIT let mut proof_vote = Codec::new(Vec::new()); From 0b1a0dbe16110442d52ead665ab85fc868a882f5 Mon Sep 17 00:00:00 2001 From: cong-or Date: Thu, 12 Oct 2023 23:26:14 +0100 Subject: [PATCH 14/14] refactor(rm unused deps): housekeeping --- Cargo.lock | 3 --- src/sign/Cargo.toml | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76084ece37..6a6f69ac0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6764,7 +6764,6 @@ dependencies = [ "chain-impl-mockchain", "chain-ser", "chain-storage", - "chain-time", "chain-vote", "clap 4.4.6", "clap_complete_command", @@ -6781,8 +6780,6 @@ dependencies = [ "serde_json", "serde_yaml", "thiserror", - "typed-bytes", - "wallet", ] [[package]] diff --git a/src/sign/Cargo.toml b/src/sign/Cargo.toml index bd8060caa1..c84fd72d20 100644 --- a/src/sign/Cargo.toml +++ b/src/sign/Cargo.toml @@ -14,9 +14,7 @@ chain-core = { path = "../chain-libs/chain-core" } chain-impl-mockchain = { path = "../chain-libs/chain-impl-mockchain" ,features= ["audit"]} chain-ser = { path = "../chain-libs/chain-ser" } chain-storage = { path = "../chain-libs/chain-storage" } -chain-time = { path = "../chain-libs/chain-time" } -wallet = { path = "../chain-wallet-libs/wallet" } -typed-bytes = { path = "../chain-libs/typed-bytes" } + hex = "0.4" cryptoxide = "0.4.2"