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

When I deploy a contract, Oasis reports " Execution failed with error: module: contracts code: 30 message: module uses floating point data or operations"" #1569

Open
readygo67 opened this issue Nov 23, 2023 · 2 comments

Comments

@readygo67
Copy link

I am trying to deploy a ecdsa signer contract on oasis. In this contract, private_key is stored in ConfidentialCell and public_key is stored in PublicCell.

Compile with "cargo build --target wasm32-unknown-unknown --release" works as expected. But when try to upload the wasm, it reports "Error: Execution failed with error: module: contracts code: 30 message: module uses floating point data or operations".

How to fix this problem?

image

The source code is below

//! A confidential hello world smart contract.
extern crate alloc;

use oasis_contract_sdk as sdk;
use oasis_contract_sdk_storage::cell::ConfidentialCell;
use oasis_contract_sdk_storage::cell::PublicCell;
use rand::{Rng, thread_rng};
use libsecp256k1::{SecretKey, PublicKey, Message};
use sha2::{Sha256,Digest};

/// All possible errors that can be returned by the contract.
///
/// Each error is a triplet of (module, code, message) which allows it to be both easily
/// human readable and also identifyable programmatically.
#[derive(Debug, thiserror::Error, sdk::Error)]
pub enum Error {
    #[error("bad request")]
    #[sdk_error(code = 1)]
    BadRequest,
}

/// All possible requests that the contract can handle.
///
/// This includes both calls and queries.
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
pub enum Request {
    #[cbor(rename = "instantiate")]
    Instantiate { },

    #[cbor(rename = "sign")]
    Sign { hash: String },

    #[cbor(rename = "public_key")]
    PublicKey { },


}

/// All possible responses that the contract can return.
///
/// This includes both calls and queries.
#[derive(Clone, Debug, Eq, PartialEq, cbor::Encode, cbor::Decode)]
pub enum Response {
    #[cbor(rename = "public_key")]
    PublicKey { public_key: String },

    #[cbor(rename = "signature")]
    Signature { signature: String },

    #[cbor(rename = "empty")]
    Empty,
}

/// The contract type.
pub struct Signer;

/// Storage cell for the counter.
const PrivateKeyCell: ConfidentialCell<[u8;32]> = ConfidentialCell::new(b"private_key");
const PublicKeyCell: PublicCell<[u8;33]> = PublicCell::new(b"public_key");

// Implementation of the sdk::Contract trait is required in order for the type to be a contract.
impl sdk::Contract for Signer {
    type Request = Request;
    type Response = Response;
    type Error = Error;

    fn instantiate<C: sdk::Context>(ctx: &mut C, request: Request) -> Result<(), Error> {
        // This method is called during the contracts.Instantiate call when the contract is first
        // instantiated. It can be used to initialize the contract state.
        match request {
            // We require the caller to always pass the Instantiate request.
            Request::Instantiate {  } => {
                // Initialize counter to specified value.
                let mut rng = thread_rng();
                let private_key = SecretKey::random(&mut rng);
                let public_key = PublicKey::from_secret_key(&private_key);
          
                PrivateKeyCell.set(ctx.confidential_store(), private_key.serialize());
                PublicKeyCell.set(ctx.public_store(), public_key.serialize_compressed());

                Ok(())
            }
            _ => Err(Error::BadRequest),
        }
    }

    fn call<C: sdk::Context>(ctx: &mut C, request: Request) -> Result<Response, Error> {
        // This method is called for each contracts.Call call. It is supposed to handle the request
        // and return a response.
        match request {
            Request::Sign {hash} => {
                if hash.len() != 32 {
                    return Err(Error::BadRequest)
                }
                let b = PrivateKeyCell.get(ctx.confidential_store()).unwrap();
                let private_key = SecretKey::parse_slice(&b).unwrap();
                
                let hash = hex::decode(hash).unwrap();
                let msg= Message::parse_slice(&hash).unwrap();
                let (sig, _) = libsecp256k1::sign(&msg, &private_key);
                Ok(Response::Signature {
                    signature: hex::encode(sig.serialize())
                })
            }
            _ => Err(Error::BadRequest),
        }
    }

    fn query<C: sdk::Context>(ctx: &mut C, request: Request) -> Result<Response, Error> {
        // This method is called for each contracts.Query query. It is supposed to handle the
        // request and return a response.
        match request {
            Request::PublicKey {} => {
                let public_key = PublicKeyCell.get(ctx.public_store()).unwrap(); 
                // Return the greeting as a response.
                Ok(Response::PublicKey {
                    public_key: hex::encode(public_key)
                })
            }
            _ => Err(Error::BadRequest),
        }
    }
}

// Create the required Wasm exports required for the contract to be runnable.
sdk::create_contract!(Signer);


#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use secp256k1::serde::Serialize;

    use super::*;

    #[test]
    fn test_generate_keypair() {
        let mut rng = thread_rng();
        let private_key = SecretKey::random(&mut rng);
        println!("private_key: {:?}", hex::encode(private_key.serialize()));

        let public_key = PublicKey::from_secret_key(&private_key);
        println!("public_key: {:?}", hex::encode(public_key.serialize_compressed()));

    }

    #[test]
    fn test_private_key_from_str() {
        let b = hex::decode("9a008c802ceed1985ee038fb5d5e45876fde1dae87a816be88ef08218a60a60b").unwrap();
        let private_key = SecretKey::parse_slice(&b).unwrap();
        println!("private_key: {:?}", hex::encode(private_key.serialize()));

        let public_key = PublicKey::from_secret_key(&private_key);
        println!("public_key: {:?}", hex::encode(public_key.serialize_compressed()));
    }

    #[test]
    fn test_sign() {
        let b = hex::decode("9a008c802ceed1985ee038fb5d5e45876fde1dae87a816be88ef08218a60a60b").unwrap();
        let private_key = SecretKey::parse_slice(&b).unwrap();
        println!("private_key: {:?}", hex::encode(private_key.serialize()));
    
        let public_key = PublicKey::from_secret_key(&private_key);
        println!("public_key: {:?}", hex::encode(public_key.serialize_compressed()));
    
        let mut hasher = Sha256::new();
        hasher.update(b"hello world");
        let hash = hasher.finalize();
        let msg = Message::parse_slice(&hash).unwrap();
        let (sig, _) = libsecp256k1::sign(&msg, &private_key);
        println!("sig: {:?}", hex::encode(sig.serialize()));
    }
}

The Cargo.toml as below

[package]
name = "ecdsa-sign"
version = "0.0.0"
edition = "2021"
license = "Apache-2.0"

[lib]
crate-type = ["cdylib"]

[dependencies]
cbor = { version = "0.5.1", package = "oasis-cbor" }
oasis-contract-sdk = { git = "https://github.com/oasisprotocol/oasis-sdk", tag = "contract-sdk/v0.3.0" }
oasis-contract-sdk-storage = { git = "https://github.com/oasisprotocol/oasis-sdk", tag = "contract-sdk/v0.3.0" }
# Third party.
thiserror = "1.0.30"
libsecp256k1 = { version = "0.7.1", features = ["std","hmac", "static-context", "lazy-static-context"] }
rand = "0.8.5"
sha2 = "0.10.8"
hex = "0.4.3"
getrandom = { version = "0.2.11", features = ["js"] }


[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = "abort"
incremental = false
overflow-checks = true
strip = true

@readygo67
Copy link
Author

I change the libsecp256k1 to k256, the same problem happens.

//! A confidential hello world smart contract.
extern crate alloc;

use oasis_contract_sdk as sdk;
use oasis_contract_sdk_storage::cell::ConfidentialCell;
use oasis_contract_sdk_storage::cell::PublicCell;
use rand::{Rng, thread_rng};
use k256::{
    ecdsa::{SigningKey, Signature, signature::Signer, VerifyingKey},
};
use sha2::{Sha256,Digest};

/// All possible errors that can be returned by the contract.
///
/// Each error is a triplet of (module, code, message) which allows it to be both easily
/// human readable and also identifyable programmatically.
#[derive(Debug, thiserror::Error, sdk::Error)]
pub enum Error {
    #[error("bad request")]
    #[sdk_error(code = 1)]
    BadRequest,
}

/// All possible requests that the contract can handle.
///
/// This includes both calls and queries.
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
pub enum Request {
    #[cbor(rename = "instantiate")]
    Instantiate { },

    #[cbor(rename = "sign")]
    Sign { hash: String },

    #[cbor(rename = "public_key")]
    PublicKey { },


}

/// All possible responses that the contract can return.
///
/// This includes both calls and queries.
#[derive(Clone, Debug, Eq, PartialEq, cbor::Encode, cbor::Decode)]
pub enum Response {
    #[cbor(rename = "public_key")]
    PublicKey { public_key: String },

    #[cbor(rename = "signature")]
    Signature { signature: String },

    #[cbor(rename = "empty")]
    Empty,
}

/// The contract type.
pub struct Secp256k1Signer;

/// Storage cell for the counter.
const PrivateKeyCell: ConfidentialCell<[u8;32]> = ConfidentialCell::new(b"private_key");
const PublicKeyCell: PublicCell<[u8;33]> = PublicCell::new(b"public_key");

// Implementation of the sdk::Contract trait is required in order for the type to be a contract.
impl sdk::Contract for Secp256k1Signer {
    type Request = Request;
    type Response = Response;
    type Error = Error;

    fn instantiate<C: sdk::Context>(ctx: &mut C, request: Request) -> Result<(), Error> {
        // This method is called during the contracts.Instantiate call when the contract is first
        // instantiated. It can be used to initialize the contract state.
        match request {
            // We require the caller to always pass the Instantiate request.
            Request::Instantiate {  } => {
                // Initialize counter to specified value.
                let mut rng = thread_rng();
                let private_key = SigningKey::random(&mut rng);
                let public_key = VerifyingKey::from(&private_key);
          
                PrivateKeyCell.set(ctx.confidential_store(), private_key.to_bytes().into());
                let binding = public_key.to_encoded_point(true);
                let b = binding.as_bytes();
                let mut b_array = [0u8; 33];
                b_array.copy_from_slice(b);
                PublicKeyCell.set(ctx.public_store(), b_array);

                Ok(())
            }
            _ => Err(Error::BadRequest),
        }
    }

    fn call<C: sdk::Context>(ctx: &mut C, request: Request) -> Result<Response, Error> {
        // This method is called for each contracts.Call call. It is supposed to handle the request
        // and return a response.
        match request {
            Request::Sign {hash} => {
                if hash.len() != 32 {
                    return Err(Error::BadRequest)
                }
                let b = PrivateKeyCell.get(ctx.confidential_store()).unwrap();
                let private_key = SigningKey::from_slice(&b).unwrap();
                
                let hash = hex::decode(hash).unwrap();
    
                let sig:Signature = private_key.sign(hash.as_slice());
                Ok(Response::Signature {
                    signature: hex::encode(sig.to_bytes())
                })
            }
            _ => Err(Error::BadRequest),
        }
    }

    fn query<C: sdk::Context>(ctx: &mut C, request: Request) -> Result<Response, Error> {
        // This method is called for each contracts.Query query. It is supposed to handle the
        // request and return a response.
        match request {
            Request::PublicKey {} => {
                let public_key = PublicKeyCell.get(ctx.public_store()).unwrap(); 
                // Return the greeting as a response.
                Ok(Response::PublicKey {
                    public_key: hex::encode(public_key)
                })
            }
            _ => Err(Error::BadRequest),
        }
    }
}

// Create the required Wasm exports required for the contract to be runnable.
sdk::create_contract!(Secp256k1Signer);



#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }


    #[test]
    fn test_generate(){
        let mut rng = thread_rng();
        let private_key = SigningKey::random(&mut rng);
        let public_key = VerifyingKey::from(&private_key);
        println!("private_key: {:?}", hex::encode(private_key.to_bytes()));
        println!("public_key: {:?}", hex::encode(public_key.to_encoded_point(true)));
    }

    #[test]
    fn test_from_bytes(){
        let b = hex::decode("fa2db56760e7c8559444eb18a9d2a9f81283fd4105f89d9002c29de9a96fdc22").unwrap();
        
        let private_key = SigningKey::from_slice(&b).unwrap();
        let public_key = VerifyingKey::from(&private_key);
        println!("private_key: {:?}", hex::encode(private_key.to_bytes()));
        println!("public_key: {:?}", hex::encode(public_key.to_encoded_point(true)));

        let b = hex::decode("02862e92f3b5d5bd23778abd4468d4fd45a0a819a3aa544732feef6ae167b99535").unwrap();

        let public_key = VerifyingKey::from_sec1_bytes(&b).unwrap();
        println!("public_key: {:?}", hex::encode(public_key.to_encoded_point(true)));
    }

    #[test]
    fn test_sign(){
        let b = hex::decode("fa2db56760e7c8559444eb18a9d2a9f81283fd4105f89d9002c29de9a96fdc22").unwrap();
        
        let private_key = SigningKey::from_slice(&b).unwrap();
        let public_key = VerifyingKey::from(&private_key);
        println!("private_key: {:?}", hex::encode(private_key.to_bytes()));
        println!("public_key: {:?}", hex::encode(public_key.to_encoded_point(true)));
        
        let message = b"ECDSA proves knowledge of a secret number in the context of a single message";
        let signature: Signature  = private_key.sign(message);
        println!("original signature: {:?}", hex::encode(signature.to_bytes()));
        
        let sig = Signature::from_slice(signature.to_bytes().as_slice()).unwrap();
        println!("recover  signature: {:?}", hex::encode(sig.to_bytes()));
    }
}
[package]
name = "ecdsa-k256-sign"
version = "0.0.0"
edition = "2021"
license = "Apache-2.0"

[lib]
crate-type = ["cdylib"]

[dependencies]
cbor = { version = "0.5.1", package = "oasis-cbor" }
oasis-contract-sdk = { git = "https://github.com/oasisprotocol/oasis-sdk", tag = "contract-sdk/v0.3.0" }
oasis-contract-sdk-storage = { git = "https://github.com/oasisprotocol/oasis-sdk", tag = "contract-sdk/v0.3.0" }
# Third party.
thiserror = "1.0.30"
k256 = { version = "0.13.1", features = ["ecdsa", "sha256", "arithmetic", "digest", "pem", "serde", "alloc"]}
rand = "0.8.5"
sha2 = "0.10.8"
hex = "0.4.3"
getrandom = { version = "0.2.11", features = ["js"] }


[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = "abort"
incremental = false
overflow-checks = true
strip = true


@readygo67
Copy link
Author

try to minimize the scope with the following code

//! A minimal hello world smart contract.
extern crate alloc;
use oasis_contract_sdk as sdk;
use oasis_contract_sdk_storage::cell::PublicCell;
use oasis_contract_sdk_storage::cell::ConfidentialCell;
use k256::{
    ecdsa::{SigningKey, /*Signature, signature::Signer,*/ VerifyingKey},
};


/// All possible errors that can be returned by the contract.
///
/// Each error is a triplet of (module, code, message) which allows it to be both easily
/// human readable and also identifyable programmatically.
#[derive(Debug, thiserror::Error, sdk::Error)]
pub enum Error {
    #[error("bad request")]
    #[sdk_error(code = 1)]
    BadRequest,
}

/// All possible requests that the contract can handle.
///
/// This includes both calls and queries.
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
pub enum Request {
    #[cbor(rename = "instantiate")]
    Instantiate {},

    #[cbor(rename = "say_hello")]
    SayHello { who: String },

    #[cbor(rename = "who")]
    Who { },

}

/// All possible responses that the contract can return.
///
/// This includes both calls and queries.
#[derive(Clone, Debug, Eq, PartialEq, cbor::Encode, cbor::Decode)]
pub enum Response {
    #[cbor(rename = "hello")]
    Hello { greeting: String },

    #[cbor(rename = "who")]
    Who {who:String},

    #[cbor(rename = "empty")]
    Empty,
}

/// The contract type.
pub struct Secp256k1Signer;

/// Storage cell for the counter.
const PrivateKeyCell: ConfidentialCell<String> = ConfidentialCell::new(b"private_key");
const PublicKeyCell: PublicCell<String> = PublicCell::new(b"public_key");

// impl HelloWorld {
//     /// Increment the counter and return the previous value.
//     fn say_hello<C: sdk::Context>(ctx: &mut C, s: String) -> String {
//         let counter = COUNTER.get(ctx.public_store()).unwrap_or_default();
//         COUNTER.set(ctx.public_store(), s);

//         counter
//     }
// }

// Implementation of the sdk::Contract trait is required in order for the type to be a contract.
impl sdk::Contract for Secp256k1Signer {
    type Request = Request;
    type Response = Response;
    type Error = Error;

    fn instantiate<C: sdk::Context>(ctx: &mut C, request: Request) -> Result<(), Error> {
        // This method is called during the contracts.Instantiate call when the contract is first
        // instantiated. It can be used to initialize the contract state.
        match request {
            // We require the caller to always pass the Instantiate request.
            Request::Instantiate { } => {
                let s = "fa2db56760e7c8559444eb18a9d2a9f81283fd4105f89d9002c29de9a96fdc22".to_string();
                PrivateKeyCell.set(ctx.confidential_store(), s.clone());

                let b = hex::decode(s.clone()).unwrap();
                // let private_key = SigningKey::from_slice(&b).unwrap();
                // let public_key:VerifyingKey = VerifyingKey::from(&private_key);
                // let binding = public_key.to_encoded_point(true);
                // let b = binding.as_bytes();

                // let s = String::from_utf8(b.to_vec()).unwrap();
                PublicKeyCell.set(ctx.public_store(), s);

                Ok(())
            }
            _ => Err(Error::BadRequest),
        }
    }

    fn call<C: sdk::Context>(ctx: &mut C, request: Request) -> Result<Response, Error> {
        // This method is called for each contracts.Call call. It is supposed to handle the request
        // and return a response.
        // match request {
        //     Request::SayHello { who } => {
        //         // Increment the counter and retrieve the previous value.
        //         let counter = Self::say_hello(ctx, who.clone());

        //         // Return the greeting as a response.
        //         Ok(Response::Hello {
        //             greeting: format!("hello {who} ({counter})"),
        //         })
        //     }
        //     _ => Err(Error::BadRequest),
        // }
        Err(Error::BadRequest)
    }

    fn query<C: sdk::Context>(ctx: &mut C, request: Request) -> Result<Response, Error> {
        // This method is called for each contracts.Query query. It is supposed to handle the
        // request and return a response.
        // match request {
        //     Request::Who {} => {
        //         let s = COUNTER.get(ctx.public_store()).unwrap_or_default();
        //         // Return the greeting as a response.
        //         Ok(Response::Who {
        //             who: s
        //         })
        //     }

        //     _ => Err(Error::BadRequest),
        // }
        Err(Error::BadRequest)
    }
}

// Create the required Wasm exports required for the contract to be runnable.
sdk::create_contract!(Secp256k1Signer);

Some new points:

  1. the f64 is introduced by "let private_key = SigningKey::from_slice(&b).unwrap()";
  2. if compile without --release, there is fp64.xx opcode and function with type f64, if compile with --release, there is no fp64.xx opcode, but only f64 type ;
  3. "soft-float" does not change the compile result;
  4. I think Error::ModuleUsesFloatingPoint is too strict, which checks only parameter type

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant