Skip to content

Commit

Permalink
Add secp256r1 support (#1246)
Browse files Browse the repository at this point in the history
### What

Add support for secp256r1 signature verification (picking up env changes
stellar/rs-soroban-env#1376).

And adapts the existing `Crypto` module by split the cryptographic
functions into two sets:
- `Crypto`: standard, recommended set of cryptographic functions. This
includes `secp256k1_recover` and `secp256r1_verify` taking the full
`message: Bytes` as input and performs hashing underneath.
- `CryptoHazmat`: hazardous material. Contains low-level, unsecure if
used incorrectly functions including `secp256k1_recover_prehash` and
`secp256r1_verify_prehash`, taking a `message_hash: BytesN<32>` as
input.

Design rationales were discussed in the env PR (started on
stellar/rs-soroban-env#1376 (comment)
and onward).

XDR changes associated with the new contract spec:
stellar/stellar-xdr#182,
stellar/stellar-xdr#183
rs-xdr: stellar/rs-stellar-xdr#361

End-to-end with secp256r1 account contract:
stellar/rs-soroban-env#1402

### Why


[TODO: Why this change is being made. Include any context required to
understand the why.]

### Known limitations

[TODO or N/A]

---------

Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com>
  • Loading branch information
jayz22 and leighmcculloch committed Apr 23, 2024
1 parent d4f79c2 commit 31d0eee
Show file tree
Hide file tree
Showing 23 changed files with 661 additions and 65 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 1 addition & 18 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,7 @@ members = [
"soroban-spec-rust",
"soroban-ledger-snapshot",
"soroban-token-sdk",
"tests/empty",
"tests/empty2",
"tests/add_u64",
"tests/add_i128",
"tests/add_u128",
"tests/import_contract",
"tests/invoke_contract",
"tests/udt",
"tests/contract_data",
"tests/events",
"tests/logging",
"tests/errors",
"tests/alloc",
"tests/auth",
"tests/fuzz",
"tests/multiimpl",
"tests/workspace_contract",
"tests/workspace_lib",
"tests/*",
]

[workspace.package]
Expand Down
2 changes: 1 addition & 1 deletion soroban-sdk-macros/src/derive_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ fn map_tuple_variant(
let spec_case = {
let field_types = fields
.iter()
.map(|f| match map_type(&f.ty) {
.map(|f| match map_type(&f.ty, false) {
Ok(t) => t,
Err(e) => {
errors.push(e);
Expand Down
16 changes: 13 additions & 3 deletions soroban-sdk-macros/src/derive_fn.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::map_type::map_type;
use itertools::MultiUnzip;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
Expand All @@ -10,7 +11,7 @@ use syn::{
};

#[allow(clippy::too_many_arguments)]
pub fn derive_fn(
pub fn derive_pub_fn(
crate_path: &Path,
call: &TokenStream2,
ident: &Ident,
Expand Down Expand Up @@ -54,7 +55,16 @@ pub fn derive_fn(
.skip(if env_input.is_some() { 1 } else { 0 })
.enumerate()
.map(|(i, a)| match a {
FnArg::Typed(_) => {
FnArg::Typed(pat_ty) => {
// If fn is a __check_auth implementation, allow the first argument,
// signature_payload of type Bytes (32 size), to be a Hash.
let allow_hash = ident == "__check_auth" && i == 0;

// Error if the type of the fn is not mappable.
if let Err(e) = map_type(&pat_ty.ty, allow_hash) {
errors.push(e);
}

let ident = format_ident!("arg_{}", i);
let arg = FnArg::Typed(PatType {
attrs: vec![],
Expand All @@ -70,7 +80,7 @@ pub fn derive_fn(
});
let call = quote! {
<_ as #crate_path::unwrap::UnwrapOptimized>::unwrap_optimized(
<_ as #crate_path::TryFromVal<#crate_path::Env, #crate_path::Val>>::try_from_val(
<_ as #crate_path::TryFromValForContractFn<#crate_path::Env, #crate_path::Val>>::try_from_val_for_contract_fn(
&env,
&#ident
)
Expand Down
12 changes: 9 additions & 3 deletions soroban-sdk-macros/src/derive_spec_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,21 @@ pub fn derive_fn_spec(
let spec_args: Vec<_> = inputs
.iter()
.skip(if env_input.is_some() { 1 } else { 0 })
.map(|a| match a {
.enumerate()
.map(|(i, a)| match a {
FnArg::Typed(pat_type) => {
let name = if let Pat::Ident(pat_ident) = *pat_type.pat.clone() {
pat_ident.ident.to_string()
} else {
errors.push(Error::new(a.span(), "argument not supported"));
"".to_string()
};
match map_type(&pat_type.ty) {

// If fn is a __check_auth implementation, allow the first argument,
// signature_payload of type Bytes (32 size), to be a Hash.
let allow_hash = ident == "__check_auth" && i == 0;

match map_type(&pat_type.ty, allow_hash) {
Ok(type_) => {
let name = name.try_into().unwrap_or_else(|_| {
const MAX: u32 = 30;
Expand Down Expand Up @@ -100,7 +106,7 @@ pub fn derive_fn_spec(

// Prepare the output.
let spec_result = match output {
ReturnType::Type(_, ty) => vec![match map_type(ty) {
ReturnType::Type(_, ty) => vec![match map_type(ty, true) {
Ok(spec) => spec,
Err(e) => {
errors.push(e);
Expand Down
3 changes: 1 addition & 2 deletions soroban-sdk-macros/src/derive_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ pub fn derive_type_struct(
) -> TokenStream2 {
// Collect errors as they are encountered and emit them at the end.
let mut errors = Vec::<Error>::new();

let fields = &data.fields;
let field_count_usize: usize = fields.len();
let (spec_fields, field_idents, field_names, field_idx_lits, try_from_xdrs, try_into_xdrs): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = fields
Expand All @@ -43,7 +42,7 @@ pub fn derive_type_struct(
errors.push(Error::new(field_ident.span(), format!("struct field name is too long: {}, max is {MAX}", field_name.len())));
StringM::<MAX>::default()
}),
type_: match map_type(&field.ty) {
type_: match map_type(&field.ty, false) {
Ok(t) => t,
Err(e) => {
errors.push(e);
Expand Down
2 changes: 1 addition & 1 deletion soroban-sdk-macros/src/derive_struct_tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub fn derive_type_struct_tuple(
let field_spec = ScSpecUdtStructFieldV0 {
doc: docs_from_attrs(&field.attrs).try_into().unwrap(), // TODO: Truncate docs, or display friendly compile error.
name: field_name.try_into().unwrap_or_else(|_| StringM::default()),
type_: match map_type(&field.ty) {
type_: match map_type(&field.ty, false) {
Ok(t) => t,
Err(e) => {
errors.push(e);
Expand Down
4 changes: 2 additions & 2 deletions soroban-sdk-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use derive_client::{derive_client_impl, derive_client_type};
use derive_enum::derive_type_enum;
use derive_enum_int::derive_type_enum_int;
use derive_error_enum_int::derive_type_error_enum_int;
use derive_fn::{derive_contract_function_registration_ctor, derive_fn};
use derive_fn::{derive_contract_function_registration_ctor, derive_pub_fn};
use derive_spec_fn::derive_fn_spec;
use derive_struct::derive_type_struct;
use derive_struct_tuple::derive_type_struct_tuple;
Expand Down Expand Up @@ -231,7 +231,7 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream {
.map(|m| {
let ident = &m.sig.ident;
let call = quote! { <super::#ty>::#ident };
derive_fn(
derive_pub_fn(
&crate_path,
&call,
ident,
Expand Down
41 changes: 25 additions & 16 deletions soroban-sdk-macros/src/map_type.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use stellar_xdr::curr as stellar_xdr;
use stellar_xdr::{
ScSpecTypeBytesN, ScSpecTypeDef, ScSpecTypeMap, ScSpecTypeOption, ScSpecTypeResult,
ScSpecTypeTuple, ScSpecTypeUdt, ScSpecTypeVec, ScSpectTypeHash,
ScSpecTypeTuple, ScSpecTypeUdt, ScSpecTypeVec,
};
use syn::{
spanned::Spanned, Error, Expr, ExprLit, GenericArgument, Lit, Path, PathArguments, PathSegment,
Type, TypePath, TypeTuple,
};

#[allow(clippy::too_many_lines)]
pub fn map_type(t: &Type) -> Result<ScSpecTypeDef, Error> {
pub fn map_type(t: &Type, allow_hash: bool) -> Result<ScSpecTypeDef, Error> {
match t {
Type::Path(TypePath {
qself: None,
Expand Down Expand Up @@ -61,8 +61,8 @@ pub fn map_type(t: &Type) -> Result<ScSpecTypeDef, Error> {
))?,
};
Ok(ScSpecTypeDef::Result(Box::new(ScSpecTypeResult {
ok_type: Box::new(map_type(ok)?),
error_type: Box::new(map_type(err)?),
ok_type: Box::new(map_type(ok, false)?),
error_type: Box::new(map_type(err, false)?),
})))
}
"Option" => {
Expand All @@ -74,7 +74,7 @@ pub fn map_type(t: &Type) -> Result<ScSpecTypeDef, Error> {
))?,
};
Ok(ScSpecTypeDef::Option(Box::new(ScSpecTypeOption {
value_type: Box::new(map_type(t)?),
value_type: Box::new(map_type(t, false)?),
})))
}
"Vec" => {
Expand All @@ -86,7 +86,7 @@ pub fn map_type(t: &Type) -> Result<ScSpecTypeDef, Error> {
))?,
};
Ok(ScSpecTypeDef::Vec(Box::new(ScSpecTypeVec {
element_type: Box::new(map_type(t)?),
element_type: Box::new(map_type(t, false)?),
})))
}
"Map" => {
Expand All @@ -98,8 +98,8 @@ pub fn map_type(t: &Type) -> Result<ScSpecTypeDef, Error> {
))?,
};
Ok(ScSpecTypeDef::Map(Box::new(ScSpecTypeMap {
key_type: Box::new(map_type(k)?),
value_type: Box::new(map_type(v)?),
key_type: Box::new(map_type(k, false)?),
value_type: Box::new(map_type(v, false)?),
})))
}
"BytesN" => {
Expand All @@ -113,14 +113,21 @@ pub fn map_type(t: &Type) -> Result<ScSpecTypeDef, Error> {
Ok(ScSpecTypeDef::BytesN(ScSpecTypeBytesN { n }))
}
"Hash" => {
let n = match args.as_slice() {
[GenericArgument::Const(Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))] => int.base10_parse()?,
[..] => Err(Error::new(
if allow_hash {
let n = match args.as_slice() {
[GenericArgument::Const(Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))] => int.base10_parse()?,
[..] => Err(Error::new(
t.span(),
"incorrect number of generic arguments, expect one for Hash<N>",
))?,
};
Ok(ScSpecTypeDef::BytesN(ScSpecTypeBytesN { n }))
} else {
Err(Error::new(
t.span(),
"incorrect number of generic arguments, expect one for BytesN<N>",
))?,
};
Ok(ScSpecTypeDef::Hash(ScSpectTypeHash { n }))
"Hash<N> can only be used in contexts where there is a guarantee that the hash has been sourced from a secure cryptographic hash function",
))
}
}
_ => Err(Error::new(
angle_bracketed.span(),
Expand All @@ -132,10 +139,12 @@ pub fn map_type(t: &Type) -> Result<ScSpecTypeDef, Error> {
}
}
Type::Tuple(TypeTuple { elems, .. }) => {
let map_type_reject_hash =
|t: &Type| -> Result<ScSpecTypeDef, Error> { map_type(t, false) };
Ok(ScSpecTypeDef::Tuple(Box::new(ScSpecTypeTuple {
value_types: elems
.iter()
.map(map_type)
.map(map_type_reject_hash)
.collect::<Result<Vec<ScSpecTypeDef>, Error>>()? // TODO: Implement conversion to VecM from iters to omit this collect.
.try_into()
.map_err(|e| {
Expand Down
4 changes: 2 additions & 2 deletions soroban-sdk/src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Auth contains types for building custom account contracts.

use crate::{contracttype, Address, BytesN, Env, Error, Symbol, Val, Vec};
use crate::{contracttype, crypto::Hash, Address, BytesN, Env, Error, Symbol, Val, Vec};

/// Context of a single authorized call performed by an address.
///
Expand Down Expand Up @@ -77,7 +77,7 @@ pub trait CustomAccountInterface {
/// Check that the signatures and auth contexts are valid.
fn __check_auth(
env: Env,
signature_payload: BytesN<32>,
signature_payload: Hash<32>,
signatures: Self::Signature,
auth_contexts: Vec<Context>,
) -> Result<(), Self::Error>;
Expand Down

0 comments on commit 31d0eee

Please sign in to comment.