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

feat(rust,zkp): Move asset id check from circuit to consensus #3686

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
7 changes: 4 additions & 3 deletions ironfish-rust/src/assets/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use crate::{errors::IronfishError, keys::PUBLIC_ADDRESS_SIZE, util::str_to_array, PublicAddress};
use byteorder::{ReadBytesExt, WriteBytesExt};
use ironfish_zkp::{
constants::{ASSET_ID_LENGTH, ASSET_ID_PERSONALIZATION},
constants::{ASSET_ID_LENGTH, ASSET_ID_PERSONALIZATION, GH_FIRST_BLOCK},
util::asset_hash_to_point,
};
use jubjub::{ExtendedPoint, SubgroupPoint};
Expand All @@ -19,7 +19,7 @@ pub const ID_LENGTH: usize = ASSET_ID_LENGTH;

/// Describes all the fields necessary for creating and transacting with an
/// asset on the Iron Fish network
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub struct Asset {
/// Name of the asset
pub(crate) name: [u8; NAME_LENGTH],
Expand Down Expand Up @@ -57,7 +57,7 @@ impl Asset {
}
}

fn new_with_nonce(
pub fn new_with_nonce(
owner: PublicAddress,
name: [u8; NAME_LENGTH],
metadata: [u8; METADATA_LENGTH],
Expand All @@ -68,6 +68,7 @@ impl Asset {
.hash_length(ASSET_ID_LENGTH)
.personal(ASSET_ID_PERSONALIZATION)
.to_state()
.update(GH_FIRST_BLOCK)
.update(&owner.public_address())
.update(&name)
.update(&metadata)
Expand Down
Binary file modified ironfish-rust/src/sapling_params/sapling-mint.params
Binary file not shown.
71 changes: 63 additions & 8 deletions ironfish-rust/src/transaction/mints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use std::io;

use bellman::{gadgets::multipack, groth16};
use bellman::groth16;
use bls12_381::{Bls12, Scalar};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use group::{Curve, GroupEncoding};
Expand All @@ -13,6 +13,7 @@ use ironfish_zkp::{
proofs::MintAsset,
redjubjub::{self, Signature},
};
use jubjub::ExtendedPoint;
use rand::thread_rng;

use crate::{assets::asset::Asset, errors::IronfishError, sapling_bls12::SAPLING, SaplingKey};
Expand Down Expand Up @@ -41,9 +42,6 @@ impl MintBuilder {
randomized_public_key: &redjubjub::PublicKey,
) -> Result<UnsignedMintDescription, IronfishError> {
let circuit = MintAsset {
name: self.asset.name,
metadata: self.asset.metadata,
nonce: self.asset.nonce,
proof_generation_key: Some(spender_key.sapling_proof_generation_key()),
public_key_randomness: Some(*public_key_randomness),
};
Expand All @@ -61,6 +59,7 @@ impl MintBuilder {
value: self.value,
authorizing_signature: blank_signature,
};
mint_description.partial_verify()?;

verify_mint_proof(
&mint_description.proof,
Expand Down Expand Up @@ -172,14 +171,39 @@ impl MintDescription {
public_inputs[0] = randomized_public_key_point.get_u();
public_inputs[1] = randomized_public_key_point.get_v();

let asset_id_bits = multipack::bytes_to_bits_le(self.asset.id().as_bytes());
let asset_id_inputs = multipack::compute_multipacking(&asset_id_bits);
public_inputs[2] = asset_id_inputs[0];
public_inputs[3] = asset_id_inputs[1];
let owner_public_address_point =
ExtendedPoint::from(self.asset.owner.transmission_key).to_affine();
public_inputs[2] = owner_public_address_point.get_u();
public_inputs[3] = owner_public_address_point.get_v();

public_inputs
}

/// A function to encapsulate any verification besides the proof itself.
/// This allows us to abstract away the details and make it easier to work
/// with. Note that this does not verify the proof, that happens in the
/// [`MintBuilder`] build function as the prover, and in
/// [`super::batch_verify_transactions`] as the verifier.
pub fn partial_verify(&self) -> Result<(), IronfishError> {
self.verify_valid_asset()?;

Ok(())
}

fn verify_valid_asset(&self) -> Result<(), IronfishError> {
let asset = Asset::new_with_nonce(
self.asset.owner,
self.asset.name,
self.asset.metadata,
self.asset.nonce,
)?;
if asset.id != self.asset.id {
return Err(IronfishError::InvalidAssetIdentifier);
}

Ok(())
}

/// Write the signature of this proof to the provided writer.
///
/// The signature is used by the transaction to calculate the signature
Expand Down Expand Up @@ -359,4 +383,35 @@ mod test {
.expect("should be able to serialize proof again");
assert_eq!(serialized_description, reserialized_description);
}

#[test]
fn test_mint_invalid_id() {
let key = SaplingKey::generate_key();
let owner = key.public_address();
let name = "name";
let metadata = "{ 'token_identifier': '0x123' }";

let asset = Asset::new(owner, name, metadata).unwrap();
let fake_asset = Asset::new(owner, name, "").unwrap();

let value = 5;

let public_key_randomness = jubjub::Fr::random(thread_rng());
let randomized_public_key = redjubjub::PublicKey(key.view_key.authorizing_key.into())
.randomize(public_key_randomness, SPENDING_KEY_GENERATOR);

let mint = MintBuilder::new(
Asset {
id: fake_asset.id,
metadata: asset.metadata,
name: asset.name,
nonce: asset.nonce,
owner: asset.owner,
},
value,
);

let unsigned_mint = mint.build(&key, &public_key_randomness, &randomized_public_key);
assert!(unsigned_mint.is_err());
}
}
2 changes: 2 additions & 0 deletions ironfish-rust/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,8 @@ pub fn batch_verify_transactions<'a>(
}

for mint in transaction.mints.iter() {
mint.partial_verify()?;

let public_inputs = mint.public_inputs(transaction.randomized_public_key());
mint_verifier.queue((&mint.proof, &public_inputs[..]));

Expand Down
3 changes: 0 additions & 3 deletions ironfish-zkp/src/bin/generate_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,6 @@ fn main() {
generate_params(
"sapling-mint",
MintAsset {
name: [0u8; 32],
metadata: [0u8; 77],
nonce: 0,
proof_generation_key: None,
public_key_randomness: None,
},
Expand Down
93 changes: 15 additions & 78 deletions ironfish-zkp/src/circuits/mint_asset.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bellman::{
gadgets::{blake2s, boolean, multipack},
gadgets::{blake2s, boolean},
Circuit,
};
use ff::PrimeField;
Expand All @@ -9,22 +9,9 @@ use zcash_proofs::{
constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR},
};

use crate::{
circuits::util::asset_id_preimage,
constants::{proof::PUBLIC_KEY_GENERATOR, ASSET_ID_PERSONALIZATION, CRH_IVK_PERSONALIZATION},
};
use crate::constants::{proof::PUBLIC_KEY_GENERATOR, CRH_IVK_PERSONALIZATION};

pub struct MintAsset {
/// Name of the asset
pub name: [u8; 32],

/// Identifier field for bridged asset address, or if a native custom asset, random bytes.
/// Metadata for the asset (ex. chain, network, token identifier)
pub metadata: [u8; 77],

/// The random byte used to ensure we get a valid asset identifier
pub nonce: u8,

/// Key required to construct proofs for a particular spending key
pub proof_generation_key: Option<ProofGenerationKey>,

Expand Down Expand Up @@ -122,44 +109,22 @@ impl Circuit<bls12_381::Scalar> for MintAsset {
&ivk,
)?;

// Create the Asset Info pre-image
let asset_id_preimage = asset_id_preimage(
&mut cs.namespace(|| "asset id preimage"),
&self.name,
&self.metadata,
&self.nonce,
&owner_public_address,
)?;

// Computed identifier bits from the given asset info
let asset_id_bits = blake2s::blake2s(
cs.namespace(|| "computation of asset id"),
&asset_id_preimage,
ASSET_ID_PERSONALIZATION,
)?;

// Ensure the id is 32 bytes
assert_eq!(asset_id_bits.len(), 256);

multipack::pack_into_inputs(cs.namespace(|| "pack asset id"), &asset_id_bits)?;
owner_public_address.inputize(cs.namespace(|| "owner public address"))?;

Ok(())
}
}

#[cfg(test)]
mod test {
use bellman::{
gadgets::{multipack, test::TestConstraintSystem},
Circuit,
};
use bellman::{gadgets::test::TestConstraintSystem, Circuit};
use ff::Field;
use group::{Curve, Group, GroupEncoding};
use group::{Curve, Group};
use jubjub::ExtendedPoint;
use rand::{rngs::StdRng, SeedableRng};
use zcash_primitives::sapling::ProofGenerationKey;

use crate::constants::{ASSET_ID_LENGTH, ASSET_ID_PERSONALIZATION, PUBLIC_KEY_GENERATOR};
use crate::constants::PUBLIC_KEY_GENERATOR;

use super::MintAsset;

Expand All @@ -176,25 +141,7 @@ mod test {
};
let incoming_view_key = proof_generation_key.to_viewing_key();
let public_address = PUBLIC_KEY_GENERATOR * incoming_view_key.ivk().0;

let name = [1u8; 32];
let metadata = [2u8; 77];
let nonce = 0;

let asset_id_hash = blake2s_simd::Params::new()
.hash_length(ASSET_ID_LENGTH)
.personal(ASSET_ID_PERSONALIZATION)
.to_state()
.update(&public_address.to_bytes())
.update(&name)
.update(&metadata)
.update(std::slice::from_ref(&nonce))
.finalize();

let asset_id = asset_id_hash.as_bytes();

let asset_id_bits = multipack::bytes_to_bits_le(asset_id);
let asset_id_inputs = multipack::compute_multipacking(&asset_id_bits);
let public_address_point = ExtendedPoint::from(public_address).to_affine();

let public_key_randomness = jubjub::Fr::random(&mut rng);
let randomized_public_key =
Expand All @@ -203,35 +150,20 @@ mod test {
let public_inputs = vec![
randomized_public_key.get_u(),
randomized_public_key.get_v(),
asset_id_inputs[0],
asset_id_inputs[1],
public_address_point.get_u(),
public_address_point.get_v(),
];

// Mint proof
let circuit = MintAsset {
name,
metadata,
nonce,
proof_generation_key: Some(proof_generation_key),
public_key_randomness: Some(public_key_randomness),
};
circuit.synthesize(&mut cs).unwrap();

assert!(cs.is_satisfied());
assert!(cs.verify(&public_inputs));
assert_eq!(cs.num_constraints(), 90783);

// Test bad inputs
let bad_asset_info_hashed = [1u8; 32];
let bad_asset_info_hashed_bits = multipack::bytes_to_bits_le(&bad_asset_info_hashed);
let bad_asset_info_hashed_inputs =
multipack::compute_multipacking(&bad_asset_info_hashed_bits);

// Bad asset info hash
let mut bad_inputs = public_inputs.clone();
bad_inputs[2] = bad_asset_info_hashed_inputs[0];

assert!(!cs.verify(&bad_inputs));
assert_eq!(cs.num_constraints(), 25341);

// Bad randomized public key
let bad_randomized_public_key_point = ExtendedPoint::random(&mut rng).to_affine();
Expand All @@ -240,6 +172,11 @@ mod test {

assert!(!cs.verify(&bad_inputs));

// Bad public address
let bad_public_address = ExtendedPoint::random(&mut rng).to_affine();
let mut bad_inputs = public_inputs.clone();
bad_inputs[2] = bad_public_address.get_u();

// Sanity check
assert!(cs.verify(&public_inputs));
}
Expand Down
29 changes: 0 additions & 29 deletions ironfish-zkp/src/circuits/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,6 @@ use crate::{
primitives::ValueCommitment,
};

pub fn asset_id_preimage<CS: bellman::ConstraintSystem<bls12_381::Scalar>>(
cs: &mut CS,
name: &[u8; 32],
metadata: &[u8; 77],
nonce: &u8,
owner_public_key: &EdwardsPoint,
) -> Result<Vec<boolean::Boolean>, SynthesisError> {
let mut combined_preimage = vec![];

combined_preimage
.extend(owner_public_key.repr(cs.namespace(|| "booleanize owner_public_key"))?);

let name_bits = slice_into_boolean_vec_le(cs.namespace(|| "booleanize name"), Some(name), 32)?;
combined_preimage.extend(name_bits);

let metadata_bits =
slice_into_boolean_vec_le(cs.namespace(|| "booleanize metadata"), Some(metadata), 77)?;
combined_preimage.extend(metadata_bits);

let nonce_bits = slice_into_boolean_vec_le(
cs.namespace(|| "booleanize nonce"),
Some(std::slice::from_ref(nonce)),
1,
)?;
combined_preimage.extend(nonce_bits);

Ok(combined_preimage)
}

pub fn slice_into_boolean_vec_le<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
mut cs: CS,
value: Option<&[u8]>,
Expand Down
6 changes: 3 additions & 3 deletions ironfish-zkp/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use jubjub::SubgroupPoint;
pub use zcash_primitives::constants::{
CRH_IVK_PERSONALIZATION, NOTE_COMMITMENT_RANDOMNESS_GENERATOR, NULLIFIER_POSITION_GENERATOR,
PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR,
VALUE_COMMITMENT_VALUE_GENERATOR,
CRH_IVK_PERSONALIZATION, GH_FIRST_BLOCK, NOTE_COMMITMENT_RANDOMNESS_GENERATOR,
NULLIFIER_POSITION_GENERATOR, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR,
VALUE_COMMITMENT_RANDOMNESS_GENERATOR, VALUE_COMMITMENT_VALUE_GENERATOR,
};

use zcash_primitives::sapling::pedersen_hash;
Expand Down
Loading