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

Verify raw Wasm in cargo contract verify #1551

Merged
merged 9 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Verify raw Wasm in cargo contract verify - [#1551](https://github.com/paritytech/cargo-contract/pull/1551)

## [4.0.2]

### Fixed
Expand Down
111 changes: 106 additions & 5 deletions crates/cargo-contract/src/cmd/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,17 @@ use contract_build::{
Verbosity,
VerbosityFlags,
};
use contract_metadata::ContractMetadata;
use contract_metadata::{
CodeHash,
ContractMetadata,
};

use std::{
fs::File,
io::{
BufReader,
Read,
},
path::PathBuf,
};

Expand All @@ -49,7 +56,11 @@ pub struct VerifyCommand {
manifest_path: Option<PathBuf>,
/// The reference Wasm contract (`*.contract`) that the workspace will be checked
/// against.
contract: PathBuf,
contract: Option<PathBuf>,
/// The reference Wasm contract binary (`*.wasm`) that the workspace will be checked
/// against.
#[clap(long, conflicts_with = "contract")]
wasm: Option<PathBuf>,
/// Denotes if output should be printed to stdout.
#[clap(flatten)]
verbosity: VerbosityFlags,
Expand All @@ -62,9 +73,100 @@ impl VerifyCommand {
pub fn run(&self) -> Result<VerificationResult> {
let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?;
let verbosity: Verbosity = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?;
if let Some(path) = &self.contract {
self.verify_contract(manifest_path, verbosity, path)
} else if let Some(path) = &self.wasm {
self.verify_wasm(manifest_path, verbosity, path)
} else {
anyhow::bail!("Either --wasm or --contract must be specified")
}
}

/// Verify `.wasm` binary.
fn verify_wasm(
&self,
manifest_path: ManifestPath,
verbosity: Verbosity,
path: &PathBuf,
) -> Result<VerificationResult> {
// 1. Read code hash binary from the path.
let file = File::open(path)
.context(format!("Failed to open contract binary {}", path.display()))?;

let mut reader = BufReader::new(file);
let mut buffer: [u8; 32] = [0; 32];
reader
.read_exact(&mut buffer)
.context(format!("Failed to read contract binary {}", path.display()))?;

let reference_code_hash = CodeHash(buffer);
SkymanOne marked this conversation as resolved.
Show resolved Hide resolved

// 2. Call `cargo contract build` in the release mode.
let args = ExecuteArgs {
manifest_path: manifest_path.clone(),
verbosity,
optimization_passes: Some(contract_build::OptimizationPasses::Z),
build_mode: BuildMode::Release,
build_artifact: BuildArtifacts::CodeOnly,
extra_lints: false,
..Default::default()
};

let build_result = execute(args)?;

// 4. Grab the code hash from the built contract and compare it with the reference
// one.
let built_wasm_path = if let Some(m) = build_result.dest_wasm {
m
} else {
// Since we're building the contract ourselves this should always be
// populated, but we'll bail out here just in case.
anyhow::bail!("\nThe workspace contract does not contain a Wasm binary,\n\
therefore we are unable to verify the contract."
.to_string()
.bright_yellow())
};

let file = File::open(&built_wasm_path).context(format!(
"Failed to open contract binary {}",
&built_wasm_path.display()
))?;

let mut reader = BufReader::new(file);
let mut buffer: [u8; 32] = [0; 32];
reader
.read_exact(&mut buffer)
.context(format!("Failed to read contract binary {}", path.display()))?;

let output_code_hash = CodeHash(buffer);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are you only reading the first 32 bytes of the Wasm binary here and then assume it's the hash?

ascjones marked this conversation as resolved.
Show resolved Hide resolved

if output_code_hash != reference_code_hash {
anyhow::bail!(format!(
"\nFailed to verify the authenticity of wasm binary at {} against the workspace \n\
found at {}.",
format!("`{}`", path.display()).bright_white(),
format!("{:?}", manifest_path.as_ref()).bright_white()).bright_red()
);
}
SkymanOne marked this conversation as resolved.
Show resolved Hide resolved

Ok(VerificationResult {
is_verified: true,
image: None,
contract: built_wasm_path.display().to_string(),
reference_contract: path.display().to_string(),
output_json: self.output_json,
verbosity,
})
}

/// Verify the `.contract` bundle.
fn verify_contract(
&self,
manifest_path: ManifestPath,
verbosity: Verbosity,
path: &PathBuf,
) -> Result<VerificationResult> {
// 1. Read the given metadata, and pull out the `BuildInfo`
let path = &self.contract;
let file = File::open(path)
.context(format!("Failed to open contract bundle {}", path.display()))?;

Expand Down Expand Up @@ -166,7 +268,7 @@ impl VerifyCommand {
)
};

let target_bundle = built_contract_path.dest_bundle;
let target_bundle = &built_contract_path.dest_bundle;

let file = File::open(target_bundle.clone()).context(format!(
"Failed to open contract bundle {}",
Expand All @@ -187,7 +289,6 @@ impl VerifyCommand {
&reference_code_hash,
&target_code_hash
);

anyhow::bail!(format!(
"\nFailed to verify the authenticity of {} contract against the workspace \n\
found at {}.",
Expand Down
Loading