Skip to content
Draft
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
22 changes: 22 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ members = [
"examples/big-data-section",
"examples/bls381",
"examples/calc-hash",
"examples/crypto-demo",
"examples/custom",
"examples/delayed-reservation-sender",
"examples/compose",
Expand Down Expand Up @@ -477,6 +478,7 @@ demo-bls381 = { path = "examples/bls381" }
demo-calc-hash = { path = "examples/calc-hash" }
demo-calc-hash-in-one-block = { path = "examples/calc-hash/in-one-block" }
demo-calc-hash-over-blocks = { path = "examples/calc-hash/over-blocks" }
demo-crypto = { path = "examples/crypto-demo" }
demo-custom = { path = "examples/custom" }
demo-delayed-reservation-sender = { path = "examples/delayed-reservation-sender" }
demo-compose = { path = "examples/compose" }
Expand Down Expand Up @@ -541,6 +543,7 @@ nix = "0.26.4" # gear-lazy-pages
ipc-channel = "0.19.0" # lazy-pages-fuzzer
itertools = { version = "0.13", default-features = false } # utils/wasm-builder
libp2p = "=0.51.4" # gcli (same version as sc-consensus)
libsecp256k1 = { version = "0.7.2", default-features = false } # core/processor, ethexe/processor — secp256k1 recover pubkey decompression
mimalloc = { version = "0.1.46", default-features = false } # node/cli
nacl = "0.5.3" # gcli
libfuzzer-sys = "0.4" # utils/runtime-fuzzer/fuzz
Expand Down
8 changes: 8 additions & 0 deletions core/backend/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,14 @@ where
add_function!(Alloc, alloc);
add_function!(Free, free);
add_function!(FreeRange, free_range);

add_function!(Blake2b256, blake2b_256);
add_function!(Sha256, sha256);
add_function!(Keccak256, keccak256);
add_function!(Sr25519Verify, sr25519_verify);
add_function!(Ed25519Verify, ed25519_verify);
add_function!(Secp256k1Verify, secp256k1_verify);
add_function!(Secp256k1Recover, secp256k1_recover);
}
}

Expand Down
204 changes: 204 additions & 0 deletions core/backend/src/funcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,210 @@ where
)
}

pub fn blake2b_256(data: Read, out: WriteAs<[u8; 32]>) -> impl Syscall<Caller> {
InfallibleSyscall::new(
CostToken::Blake2b256(data.size().into()),
move |ctx: &mut MemoryCallerContext<Caller>| {
let data: RuntimeBuffer = data
.into_inner()?
.try_into()
.map_err(|LimitedVecError| {
UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into()
})
.map_err(TrapExplanation::UnrecoverableExt)?;

let hash = ctx.caller_wrap.ext_mut().blake2b_256(data.as_slice())?;

out.write(ctx, &hash).map_err(Into::into)
},
)
}

pub fn sha256(data: Read, out: WriteAs<[u8; 32]>) -> impl Syscall<Caller> {
InfallibleSyscall::new(
CostToken::Sha256(data.size().into()),
move |ctx: &mut MemoryCallerContext<Caller>| {
let data: RuntimeBuffer = data
.into_inner()?
.try_into()
.map_err(|LimitedVecError| {
UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into()
})
.map_err(TrapExplanation::UnrecoverableExt)?;

let hash = ctx.caller_wrap.ext_mut().sha256(data.as_slice())?;

out.write(ctx, &hash).map_err(Into::into)
},
)
}

pub fn keccak256(data: Read, out: WriteAs<[u8; 32]>) -> impl Syscall<Caller> {
InfallibleSyscall::new(
CostToken::Keccak256(data.size().into()),
move |ctx: &mut MemoryCallerContext<Caller>| {
let data: RuntimeBuffer = data
.into_inner()?
.try_into()
.map_err(|LimitedVecError| {
UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into()
})
.map_err(TrapExplanation::UnrecoverableExt)?;

let hash = ctx.caller_wrap.ext_mut().keccak256(data.as_slice())?;

out.write(ctx, &hash).map_err(Into::into)
},
)
}

pub fn sr25519_verify(
pk: ReadAs<[u8; 32]>,
context: Read,
msg: Read,
sig: ReadAs<[u8; 64]>,
out: WriteAs<u8>,
) -> impl Syscall<Caller> {
// Transcript bytes = ctx || msg. Schnorrkel's merlin append
// cost scales linearly in (ctx_len + msg_len), so gas scales
// with the same sum.
let transcript_len = context.size().saturating_add(msg.size());
InfallibleSyscall::new(
CostToken::Sr25519Verify(transcript_len.into()),
move |ctx: &mut MemoryCallerContext<Caller>| {
let pk = pk.into_inner()?;
let sig = sig.into_inner()?;
let context: RuntimeBuffer = context
.into_inner()?
.try_into()
.map_err(|LimitedVecError| {
UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into()
})
.map_err(TrapExplanation::UnrecoverableExt)?;
let msg: RuntimeBuffer = msg
.into_inner()?
.try_into()
.map_err(|LimitedVecError| {
UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into()
})
.map_err(TrapExplanation::UnrecoverableExt)?;

let ok = ctx.caller_wrap.ext_mut().sr25519_verify(
&pk,
context.as_slice(),
msg.as_slice(),
&sig,
)?;

out.write(ctx, &u8::from(ok)).map_err(Into::into)
},
)
}

pub fn ed25519_verify(
pk: ReadAs<[u8; 32]>,
msg: Read,
sig: ReadAs<[u8; 64]>,
out: WriteAs<u8>,
) -> impl Syscall<Caller> {
let msg_len = msg.size();
InfallibleSyscall::new(
CostToken::Ed25519Verify(msg_len.into()),
move |ctx: &mut MemoryCallerContext<Caller>| {
let pk = pk.into_inner()?;
let sig = sig.into_inner()?;
let msg: RuntimeBuffer = msg
.into_inner()?
.try_into()
.map_err(|LimitedVecError| {
UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into()
})
.map_err(TrapExplanation::UnrecoverableExt)?;

let ok = ctx
.caller_wrap
.ext_mut()
.ed25519_verify(&pk, msg.as_slice(), &sig)?;

out.write(ctx, &u8::from(ok)).map_err(Into::into)
},
)
}

pub fn secp256k1_verify(
msg_hash: ReadAs<[u8; 32]>,
sig: ReadAs<[u8; 65]>,
pk: ReadAs<[u8; 33]>,
malleability_flag: u32,
out: WriteAs<u8>,
) -> impl Syscall<Caller> {
InfallibleSyscall::new(
CostToken::Secp256k1Verify,
move |ctx: &mut MemoryCallerContext<Caller>| {
let msg_hash = msg_hash.into_inner()?;
let sig = sig.into_inner()?;
let pk = pk.into_inner()?;

// Reject unknown malleability_flag values at the wrapper
// layer. Must match ethexe's host-fn behavior (see
// ethexe/processor/src/host/api/crypto.rs::secp256k1_verify)
// or the same (sig, flag) pair gives different answers on
// the two networks — exactly the consistency guarantee the
// shared low-s helper exists to uphold.
if malleability_flag > 1 {
return out.write(ctx, &0u8).map_err(Into::into);
}

let ok = ctx.caller_wrap.ext_mut().secp256k1_verify(
&msg_hash,
&sig,
&pk,
malleability_flag,
)?;

out.write(ctx, &u8::from(ok)).map_err(Into::into)
},
)
}

pub fn secp256k1_recover(
msg_hash: ReadAs<[u8; 32]>,
sig: ReadAs<[u8; 65]>,
malleability_flag: u32,
out_pk: WriteAs<[u8; 65]>,
err: WriteAs<u32>,
) -> impl Syscall<Caller> {
InfallibleSyscall::new(
CostToken::Secp256k1Recover,
move |ctx: &mut MemoryCallerContext<Caller>| {
let msg_hash = msg_hash.into_inner()?;
let sig = sig.into_inner()?;

// An unknown flag value is rejected at the wrapper layer
// without touching the curve math. Distinct error code
// from "malformed sig" and "high-s rejected" so callers
// can tell typos from policy from data errors.
if malleability_flag > 1 {
out_pk.write(ctx, &[0u8; 65])?;
return err.write(ctx, &3u32).map_err(Into::into);
}

let recovered = ctx.caller_wrap.ext_mut().secp256k1_recover(
&msg_hash,
&sig,
malleability_flag,
)?;

let (err_code, pk_bytes) = match recovered {
Some(pk) => (0u32, pk),
None => (1u32, [0u8; 65]),
};
out_pk.write(ctx, &pk_bytes)?;
err.write(ctx, &err_code).map_err(Into::into)
},
)
}

pub fn panic(data: ReadPayloadLimited) -> impl Syscall<Caller> {
InfallibleSyscall::new(
CostToken::Null,
Expand Down
43 changes: 43 additions & 0 deletions core/backend/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,49 @@ impl Externalities for MockExt {
fn debug(&self, _data: &str) -> Result<(), Self::UnrecoverableError> {
Ok(())
}
fn blake2b_256(&self, _data: &[u8]) -> Result<[u8; 32], Self::UnrecoverableError> {
Ok([0u8; 32])
}
fn sha256(&self, _data: &[u8]) -> Result<[u8; 32], Self::UnrecoverableError> {
Ok([0u8; 32])
}
fn keccak256(&self, _data: &[u8]) -> Result<[u8; 32], Self::UnrecoverableError> {
Ok([0u8; 32])
}
fn sr25519_verify(
&self,
_pk: &[u8; 32],
_ctx: &[u8],
_msg: &[u8],
_sig: &[u8; 64],
) -> Result<bool, Self::UnrecoverableError> {
Ok(false)
}
fn ed25519_verify(
&self,
_pk: &[u8; 32],
_msg: &[u8],
_sig: &[u8; 64],
) -> Result<bool, Self::UnrecoverableError> {
Ok(false)
}
fn secp256k1_verify(
&self,
_msg_hash: &[u8; 32],
_sig: &[u8; 65],
_pk: &[u8; 33],
_malleability_flag: u32,
) -> Result<bool, Self::UnrecoverableError> {
Ok(false)
}
fn secp256k1_recover(
&self,
_msg_hash: &[u8; 32],
_sig: &[u8; 65],
_malleability_flag: u32,
) -> Result<Option<[u8; 65]>, Self::UnrecoverableError> {
Ok(None)
}
fn size(&self) -> Result<usize, Self::UnrecoverableError> {
Ok(0)
}
Expand Down
5 changes: 4 additions & 1 deletion core/processor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ log.workspace = true
derive_more.workspace = true
actor-system-error.workspace = true
parity-scale-codec = { workspace = true, features = ["derive"] }
sp-core = { workspace = true }
libsecp256k1 = { workspace = true, features = ["static-context"] }
schnorrkel = { version = "0.11.4", default-features = false }
gear-workspace-hack.workspace = true

[dev-dependencies]
Expand All @@ -31,7 +34,7 @@ gear-core = { workspace = true, features = ["mock"] }

[features]
default = ["std"]
std = ["gear-core-backend/std", "gear-wasm-instrument/std"]
std = ["gear-core-backend/std", "gear-wasm-instrument/std", "sp-core/std", "libsecp256k1/std", "schnorrkel/std"]
strict = []
mock = ["gear-core/mock"]
gtest = []
Loading
Loading