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
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.

3 changes: 3 additions & 0 deletions pallets/vector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ sp-core = { workspace = true, default-features = false }
frame-benchmarking = { workspace = true, default-features = false, optional = true }
sp1-verifier = { version = "5.0.0", default-features = false }
alloy-sol-types = { version = "0.8.12", default-features = false }
hex = { version = "*", default-features = false }
gnark-bn-verifier = { git = "https://github.com/availproject/gnark-bn-verifier", branch = "no-std", default-features = false }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move all the new dependencies to the main cargo.toml file and here just reference it with { workspace = true, default-features = false }. If you have the will could you do the same for sp1-verifier and alloy-sol-types?

substrate-bn-succinct = { version = "0.6.0-v5.0.0", default-features = false }

ark-bn254.workspace = true
ark-groth16.workspace = true
Expand Down
Binary file added pallets/vector/pico_vk.bin
Binary file not shown.
135 changes: 125 additions & 10 deletions pallets/vector/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ pub mod pallet {
SyncCommitteeStartMismatch,
/// Mock is not enabled.
MockIsNotEnabled,
/// Pico VK cannot be embedded
BadPicoVkKey,
/// Pico proof decoding error.
CannotDecodePicoProof,
/// Pico proof cannot be embedded
BadPicoProof,
}

#[pallet::event]
Expand Down Expand Up @@ -192,8 +198,12 @@ pub mod pallet {
},
/// Emit new updater.
NewUpdater { old: H256, new: H256 },
/// Emit new updater.
NewPicoUpdater { old: H256, new: H256 },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move the newly created events to the bottom? thank you

/// Emit new SP1 verification key.
NewSP1VerificationKey { old: H256, new: H256 },
/// Emit new Pico verification key.
NewPicoVerificationKey { old: H256, new: H256 },
/// Emit when new sync committee is updated.
SyncCommitteeHashUpdated { period: u64, hash: H256 },
/// Emit when mocks are enabled or disabled
Expand Down Expand Up @@ -289,6 +299,11 @@ pub mod pallet {
#[pallet::getter(fn updater)]
pub type Updater<T: Config> = StorageValue<_, H256, ValueQuery>;

/// Updater that can submit updates
#[pallet::storage]
#[pallet::getter(fn pico_updater)]
pub type PicoUpdater<T: Config> = StorageValue<_, H256, ValueQuery>;
Copy link
Contributor

@markopoloparadox markopoloparadox Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue but we should keep on mind that we need to set these storage (PicoUpdater, PicoVerificationKey) once we do the runtime upgrade


/// Maps from a period to the the sync committee hash.
#[pallet::storage]
#[pallet::getter(fn sync_committee_hashes)]
Expand All @@ -299,6 +314,10 @@ pub mod pallet {
#[pallet::getter(fn sp1_verification_key)]
pub type SP1VerificationKey<T: Config> = StorageValue<_, H256, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn pico_verification_key)]
pub type PicoVerificationKey<T: Config> = StorageValue<_, H256, ValueQuery>;

/// Enable mock functions
#[pallet::storage]
#[pallet::getter(fn verification_disabled)]
Expand Down Expand Up @@ -445,6 +464,10 @@ pub mod pallet {
Weight::zero()
}
}
enum UType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue but style-wise it might make sense to move this type definition to the top of the pallet mod section.

Sp1,
Pico,
}

#[pallet::call]
impl<T: Config> Pallet<T>
Expand Down Expand Up @@ -841,8 +864,18 @@ pub mod pallet {
proof: ProofInput,
public_values: PublicValuesInput,
) -> DispatchResultWithPostInfo {
use substrate_bn_succinct::Fr;
let sender: [u8; 32] = ensure_signed(origin)?.into();
let updater = Updater::<T>::get();
let pico_updater = PicoUpdater::<T>::get();
let sp1_updater = Updater::<T>::get();
let (updater, updater_type) = if H256(sender) == pico_updater {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue but style-wise would a match operator be more elegant in this instance?

(pico_updater, UType::Pico)
} else if H256(sender) == sp1_updater {
(sp1_updater, UType::Sp1)
} else {
return Err(Error::<T>::UpdaterMisMatch.into());
};

// ensure sender is preconfigured
ensure!(H256(sender) == updater, Error::<T>::UpdaterMisMatch);

Expand All @@ -865,14 +898,44 @@ pub mod pallet {
Error::<T>::SyncCommitteeStartMismatch
);

let sp1_vk = SP1VerificationKey::<T>::get();
let is_valid = Groth16Verifier::verify(
&proof,
&public_values,
&format!("{:?}", sp1_vk),
&GROTH16_VK_BYTES,
);
ensure!(is_valid.is_ok(), Error::<T>::VerificationFailed);
match updater_type {
UType::Sp1 => {
let sp1_vk = SP1VerificationKey::<T>::get();
let is_valid = Groth16Verifier::verify(
&proof,
&public_values,
&format!("{:?}", sp1_vk),
&GROTH16_VK_BYTES,
);
ensure!(is_valid.is_ok(), Error::<T>::VerificationFailed);
},
UType::Pico => {
let riscv_vk = PicoVerificationKey::<T>::get();

let vk_bin = include_bytes!("../pico_vk.bin");
let vk = gnark_bn_verifier::vk::Groth16VKey::try_from(vk_bin.as_slice());
ensure!(vk.is_ok(), Error::<T>::BadPicoVkKey);
Copy link
Contributor

@markopoloparadox markopoloparadox Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we could use let..else pattern together with return Error to avoid the unwrap down below.

let vk = vk.unwrap();

let groth16_proof =
gnark_bn_verifier::proof::Groth16Proof::try_from(&proof[..])
.map_err(|_| Error::<T>::BadPicoProof)?;

let pub_input_hash = hash_public_inputs(public_values.as_slice());

let mut vkh: Vec<u8> = riscv_vk.0.into();
vkh.extend(&pub_input_hash);

let public_inputs = vkh
.chunks(32)
.map(|e| Fr::from_slice(e).unwrap())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scary unwrap here

.collect::<Vec<_>>();

let is_valid = groth16_proof.verify(&vk, public_inputs.as_slice());

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scary empty space

ensure!(is_valid.is_ok(), Error::<T>::VerificationFailed);
},
};

Head::<T>::set(new_head);
let header = Headers::<T>::get(new_head);
Expand Down Expand Up @@ -990,7 +1053,16 @@ pub mod pallet {
);

let sender: [u8; 32] = ensure_signed(origin)?.into();
let updater = Updater::<T>::get();
let pico_updater = PicoUpdater::<T>::get();
let sp1_updater = Updater::<T>::get();
let (updater, _updater_type) = if H256(sender) == pico_updater {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this _updater_type used anywhere?

(pico_updater, UType::Pico)
} else if H256(sender) == sp1_updater {
(sp1_updater, UType::Sp1)
} else {
return Err(Error::<T>::UpdaterMisMatch.into());
};

// ensure sender is preconfigured
ensure!(H256(sender) == updater, Error::<T>::UpdaterMisMatch);

Expand Down Expand Up @@ -1073,6 +1145,32 @@ pub mod pallet {

Ok(().into())
}

#[pallet::call_index(18)]
#[pallet::weight(T::WeightInfo::set_pico_updater())]
pub fn set_pico_updater(origin: OriginFor<T>, updater: H256) -> DispatchResult {
ensure_root(origin)?;
let old = PicoUpdater::<T>::get();
PicoUpdater::<T>::set(updater);

Self::deposit_event(Event::<T>::NewPicoUpdater { old, new: updater });
Ok(())
}

#[pallet::call_index(19)]
#[pallet::weight(T::WeightInfo::set_pico_verification_key())]
pub fn set_pico_verification_key(origin: OriginFor<T>, pico_vk: H256) -> DispatchResult {
ensure_root(origin)?;
let old_vk = PicoVerificationKey::<T>::get();
PicoVerificationKey::<T>::put(pico_vk);

Self::deposit_event(Event::NewPicoVerificationKey {
old: old_vk,
new: pico_vk,
});

Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -1347,6 +1445,23 @@ pub mod pallet {
}
}

pub fn hash_public_inputs(public_inputs: &[u8]) -> [u8; 32] {
hash_public_inputs_with_fn(public_inputs, sp_io::hashing::sha2_256)
}

pub fn hash_public_inputs_with_fn<F>(public_inputs: &[u8], hasher: F) -> [u8; 32]
where
F: Fn(&[u8]) -> [u8; 32],
{
let mut result = hasher(public_inputs);

// The Plonk and Groth16 verifiers operate over a 254 bit field, so we need to zero
// out the first 3 bits. The same logic happens in the SP1 Ethereum verifier contract.
result[0] &= 0x1F;

result
}

impl<T: Config> ProvidePostInherent for Pallet<T>
where
[u8; 32]: From<T::AccountId>,
Expand Down
Loading