Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 full-node/src/consensus_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3503,6 +3503,9 @@ pub async fn runtime_call(
runtime_call::RuntimeCall::SignatureVerification(sig) => {
call = sig.verify_and_resume();
}
runtime_call::RuntimeCall::EcdsaPublicKeyRecover(sig) => {
call = sig.verify_and_resume();
}
runtime_call::RuntimeCall::Offchain(_) => {
// Offchain storage calls are forbidden.
return Err(RuntimeCallError::ForbiddenHostFunction);
Expand Down
3 changes: 3 additions & 0 deletions full-node/src/json_rpc_service/requests_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ pub fn spawn_requests_handler(config: Config) {
executor::runtime_call::RuntimeCall::SignatureVerification(req) => {
call = req.verify_and_resume();
}
executor::runtime_call::RuntimeCall::EcdsaPublicKeyRecover(req) => {
call = req.verify_and_resume();
}
executor::runtime_call::RuntimeCall::Offchain(_) => {
request.fail(service::ErrorResponse::InternalError);
break;
Expand Down
3 changes: 3 additions & 0 deletions lib/src/chain/chain_information/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,9 @@ impl ChainInformationBuild {
runtime_call::RuntimeCall::SignatureVerification(sig) => {
call = sig.verify_and_resume();
}
runtime_call::RuntimeCall::EcdsaPublicKeyRecover(sig) => {
call = sig.verify_and_resume();
}
runtime_call::RuntimeCall::OffchainStorageSet(req) => {
// Do nothing.
call = req.resume();
Expand Down
261 changes: 165 additions & 96 deletions lib/src/executor/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,10 @@ pub enum HostVm {
/// Need to verify whether a signature is valid.
#[from]
SignatureVerification(SignatureVerification),
/// Need to verify whether an ECDSA signature is valid and provide the corresponding
/// public key.
#[from]
EcdsaPublicKeyRecover(EcdsaPublicKeyRecover),
/// Need to call `Core_version` on the given Wasm code and return the raw output (i.e.
/// still SCALE-encoded), or an error if the call has failed.
#[from]
Expand Down Expand Up @@ -718,6 +722,7 @@ impl HostVm {
HostVm::OffchainRandomSeed(inner) => inner.inner.into_prototype(),
HostVm::OffchainSubmitTransaction(inner) => inner.inner.into_prototype(),
HostVm::SignatureVerification(inner) => inner.inner.into_prototype(),
HostVm::EcdsaPublicKeyRecover(inner) => inner.inner.into_prototype(),
HostVm::CallRuntimeVersion(inner) => inner.inner.into_prototype(),
HostVm::StartStorageTransaction(inner) => inner.inner.into_prototype(),
HostVm::EndStorageTransaction { resume, .. } => resume.inner.into_prototype(),
Expand Down Expand Up @@ -1631,104 +1636,17 @@ impl ReadyToRun {
}

HostFunction::ext_crypto_secp256k1_ecdsa_recover_version_1
| HostFunction::ext_crypto_secp256k1_ecdsa_recover_version_2 => {
let sig = expect_pointer_constant_size!(0, 65);
let msg = expect_pointer_constant_size!(1, 32);
let is_v2 = matches!(
host_fn,
HostFunction::ext_crypto_secp256k1_ecdsa_recover_version_2
);

let result = {
let rs = if is_v2 {
libsecp256k1::Signature::parse_standard_slice(&sig[0..64])
} else {
libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64])
};

if let Ok(rs) = rs {
let v = libsecp256k1::RecoveryId::parse(if sig[64] > 26 {
sig[64] - 27
} else {
sig[64]
});

if let Ok(v) = v {
let pubkey = libsecp256k1::recover(
&libsecp256k1::Message::parse_slice(&msg)
.unwrap_or_else(|_| unreachable!()),
&rs,
&v,
);

if let Ok(pubkey) = pubkey {
let mut res = Vec::with_capacity(65);
res.push(0);
res.extend_from_slice(&pubkey.serialize()[1..65]);
res
} else {
vec![1, 2]
}
} else {
vec![1, 1]
}
} else {
vec![1, 0]
}
};

self.inner
.alloc_write_and_return_pointer_size(host_fn.name(), iter::once(&result))
}
HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_1
| HostFunction::ext_crypto_secp256k1_ecdsa_recover_version_2
| HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_1
| HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_2 => {
let sig = expect_pointer_constant_size!(0, 65);
let msg = expect_pointer_constant_size!(1, 32);
let is_v2 = matches!(
let (message_ptr, message_size) = expect_pointer_size_raw!(1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@tomaka I tried but got unreachable at this line

message_ptr: expect_pointer_constant_size_raw!(1, 32),
message_size: 32,

seems to fix the panic

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

although I still don't know how we can create a mocked signature with valid public

HostVm::EcdsaPublicKeyRecover(EcdsaPublicKeyRecover {
signature_ptr: expect_pointer_constant_size_raw!(0, 65),
message_ptr,
message_size,
inner: self.inner,
host_fn,
HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_2
);

let result = {
let rs = if is_v2 {
libsecp256k1::Signature::parse_standard_slice(&sig[0..64])
} else {
libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64])
};

if let Ok(rs) = rs {
let v = libsecp256k1::RecoveryId::parse(if sig[64] > 26 {
sig[64] - 27
} else {
sig[64]
});

if let Ok(v) = v {
let pubkey = libsecp256k1::recover(
&libsecp256k1::Message::parse_slice(&msg)
.unwrap_or_else(|_| unreachable!()),
&rs,
&v,
);

if let Ok(pubkey) = pubkey {
let mut res = Vec::with_capacity(34);
res.push(0);
res.extend_from_slice(&pubkey.serialize_compressed());
res
} else {
vec![1, 2]
}
} else {
vec![1, 1]
}
} else {
vec![1, 0]
}
};

self.inner
.alloc_write_and_return_pointer_size(host_fn.name(), iter::once(&result))
})
}
HostFunction::ext_crypto_start_batch_verify_version_1 => {
if self.inner.signatures_batch_verification.is_some() {
Expand Down Expand Up @@ -3171,6 +3089,157 @@ impl fmt::Debug for SignatureVerification {
}
}

/// Must verify whether a signature is correct and return the corresponding public key.
pub struct EcdsaPublicKeyRecover {
inner: Box<Inner>,
/// Pointer to the signature and public key tag. Always 65 bytes. Guaranteed to be in range.
signature_ptr: u32,
/// Pointer to the message. Guaranteed to be in range.
message_ptr: u32,
/// Size of the message. Guaranteed to be in range.
message_size: u32,
/// Which host function this is.
host_fn: HostFunction,
}

impl EcdsaPublicKeyRecover {
/// Returns the message that the signature is expected to sign.
pub fn message(&'_ self) -> impl AsRef<[u8]> + '_ {
self.inner
.vm
.read_memory(self.message_ptr, self.message_size)
.unwrap_or_else(|_| unreachable!())
}

/// Returns the signature and public key tag.
///
/// Always 65 bytes long.
///
/// > **Note**: Be aware that this signature is untrusted input and might not be part of the
/// > set of valid signatures.
pub fn signature(&'_ self) -> impl AsRef<[u8]> + '_ {
self.inner
.vm
.read_memory(self.signature_ptr, 65)
.unwrap_or_else(|_| unreachable!())
}

/// Returns how the function would normally proceed.
pub fn normal_outcome(&self) -> Result<[u8; 65], EcdsaPublicKeyRecoverError> {
self.normal_outcome_inner().map(|pk| pk.serialize())
}

fn normal_outcome_inner(&self) -> Result<libsecp256k1::PublicKey, EcdsaPublicKeyRecoverError> {
let sig = self.signature();
let sig = sig.as_ref();
let msg = self.message();
let msg = msg.as_ref();

let is_v2 = matches!(
self.host_fn,
HostFunction::ext_crypto_secp256k1_ecdsa_recover_version_2
| HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_2
);

let rs = if is_v2 {
libsecp256k1::Signature::parse_standard_slice(&sig[0..64])
} else {
libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64])
};

let Ok(rs) = rs else {
return Err(EcdsaPublicKeyRecoverError::IncorrectRSValue);
};

let Ok(v) =
libsecp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] })
else {
return Err(EcdsaPublicKeyRecoverError::IncorrectVValue);
};

let Ok(pubkey) = libsecp256k1::recover(
&libsecp256k1::Message::parse_slice(&msg).unwrap_or_else(|_| unreachable!()),
&rs,
&v,
) else {
return Err(EcdsaPublicKeyRecoverError::InvalidSignature);
};

Ok(pubkey)
}

/// Verify the signature and resume execution.
///
/// This is equivalent to calling `resume(normal_outcome())`.
pub fn verify_and_resume(self) -> HostVm {
let outcome = self.normal_outcome_inner();
self.resume_inner(outcome)
}

/// Resume the execution, indicating the public key.
///
/// > **Note**: You are strongly encouraged to call
/// > [`EcdsaPublicKeyRecover::verify_and_resume`]. This function is meant to be
/// > used only in debugging situations.
///
/// # Panic
///
/// Panics if the public key isn't a valid ECDSA public key.
///
pub fn resume(self, result: Result<&[u8; 65], EcdsaPublicKeyRecoverError>) -> HostVm {
let result = result.map(|pk| libsecp256k1::PublicKey::parse(pk).unwrap());
self.resume_inner(result)
}

fn resume_inner(
self,
result: Result<libsecp256k1::PublicKey, EcdsaPublicKeyRecoverError>,
) -> HostVm {
let is_compressed_variant = matches!(
self.host_fn,
HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_1
| HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_2
);

let result = match result {
Ok(pubkey) => {
let mut res = Vec::with_capacity(65);
res.push(0);
if !is_compressed_variant {
res.extend_from_slice(&pubkey.serialize()[1..65]);
} else {
res.extend_from_slice(&pubkey.serialize_compressed());
}
res
}
Err(EcdsaPublicKeyRecoverError::IncorrectRSValue) => vec![1, 0],
Err(EcdsaPublicKeyRecoverError::IncorrectVValue) => vec![1, 1],
Err(EcdsaPublicKeyRecoverError::InvalidSignature) => vec![1, 2],
};

self.inner
.alloc_write_and_return_pointer_size(self.host_fn.name(), iter::once(&result))
}
}

impl fmt::Debug for EcdsaPublicKeyRecover {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("SignatureVerification")
.field("message", &self.message().as_ref())
.field("signature", &self.signature().as_ref())
.finish()
}
}

/// Error that can be returned to the virtual machine in case of error in the ECDSA signature
/// verification.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum EcdsaPublicKeyRecoverError {
IncorrectRSValue,
IncorrectVValue,
InvalidSignature,
}

/// Must provide the runtime version obtained by calling the `Core_version` entry point of a Wasm
/// blob.
pub struct CallRuntimeVersion {
Expand Down
Loading