Announcing ic-siwe: Use Ethereum wallets to login to IC

Hey! I have written about ic_siwe before but now it is time to more formally announce the project and let you know it is ready to be tried and tested :slight_smile:

ic-siwe is a project that enables Ethereum wallet-based authentication for applications on the Internet Computer (IC) platform. The goal of the project is to enhance the interoperability between Ethereum and the Internet Computer platform, enabling developers to build applications that leverage the strengths of both platforms.

Features

  • Ethereum Wallet Sign-In: Enables Ethereum wallet sign-in for IC applications. Sign in with any eth wallet to generate an IC identity and session.

  • Session Identity Uniqueness: Ensures that session identities are specific to each application’s context, preventing cross-app identity misuse.

  • Consistent Principal Generation: Guarantees that logging in with an Ethereum wallet consistently produces the same Principal, irrespective of the client used.

  • Direct Ethereum Address to Principal Mapping: Creates a one-to-one correlation between Ethereum addresses and Principals within the scope of the current application.

  • Timebound Sessions: Allows developers to set expiration times for sessions, enhancing security and control.

  • Prebuilt Identity Provider: Provides a prebuilt canister that can be integrated into any Internet Computer application, independent of the application’s programming language.

Usage

Developers have two options to use SIWE in their IC applications:

  1. Use the prebuilt ic_siwe_provider canister: This is the easiest way to integrate SIWE into an Internet Computer application. The pre-built canister is added to the project dfx.json and then configured to meet the needs of the application. ic_siwe_provider can be added to any Internet Computer application, independent of the application’s programming language.

  2. Use the ic_siwe library: This allows developers full control over the SIWE integration. The ic_siwe Rust library provides all the necessary tools for integrating SIWE into IC canisters.

SIWE login flow

The below diagram illustrates the high-level login flow when using the ic_siwe_provider canister.

  1. An ICP application requests a SIWE message from the ic_siwe_provider canister on behalf of the user.

  2. The application displays the SIWE message to the user who signs it with their Ethereum wallet.

  3. The application sends the signed SIWE message to the ic_siwe_provider canister to login the user. The canister verifies the signature and creates an identity for the user.

  4. The application retrieves the identity from the ic_siwe_provider canister.

  5. The application can now use the identity to make authenticated calls to canisters.

Resources

ic-siwe consists of two main packages: the Rust support library and the prebuilt identity provider canister. The project also includes React demo applications and React hooks for easy frontend integration with SIWE enabled Internet Computer canisters.

ic_siwe

Rust library that provides the necessary tools for integrating Sign-In with Ethereum (SIWE) into IC canisters, allowing users to sign in using their Ethereum wallets.

ic-siwe-provider

Prebuilt canister serving as a SIWE identity provider for Internet Computer canisters. ic_siwe-provider packages the ic_siwe library and makes it available as a canister that can easily be integrated into any Internet Computer application, independent of the application’s programming language.

ic-siwe-react-demo-rust

React demo application that demonstrates how to integrate SIWE into an Internet Computer canister using the ic-use-siwe-identity hook and ic-siwe-provider canister.

Try the deployed demo here: https://shtr2-2iaaa-aaaal-qckva-cai.icp0.io

ic-siwe-react-demo-ts

Same demo as above but this time the backend canister is written in TypeScript using Azle.

Try the deployed demo here: https://zwsg3-myaaa-aaaal-qdf7q-cai.icp0.io/

ic-use-siwe-identity

React hook and context provider for easy frontend integration with SIWE enabled Internet Computer canisters.

ic-use-actor

React hook and context provider for managing Internet Computer (IC) actors with features like type safety and request/response interceptors. ic-use-actor makes interacting with Internet Computer canisters more fun!

License

This project is licensed under the MIT License.

Future Plans

The project is still in active development. Before using ic-siwe in production, I would like to do a more formal security audit.

Also, I want to integrate SIWE into more demo applications, ideally some wallet application.

Most likely, there are features missing in the current implementation. If you have any ideas or requests for features, please let me know by opening an issue on the GitHub repository.

17 Likes

Hey! I just had a super quick look at your code, I already thought about this idea and like how you did it. I’ll try to dive a bit more into the code.

For such a critical canister I hope you’ll get a proper audit, are you already in contact with some teams? Maybe someone from dfinity that knows Internet Identity could look at your code.

2 Likes

I am sure @frederikrothenberger would have some great comments :grinning:. Large parts of the delegation creation code is copied from II.

1 Like

Cool, I also pinged someone else from the II team to take a look.

3 Likes

Sounds great. Is there a description in greater detail of the steps that you outlined above and the diagram?

1 Like

In the ic_siwe readme the flow is described in a little greater detail. The whole flow is taken care of by these three methods in the provider canister:

service : (settings_input : SettingsInput) -> {
  "siwe_prepare_login" : (Address) -> (PrepareLoginResponse);
  "siwe_login" : (SiweSignature, Address, SessionKey) -> (LoginResponse);
  "siwe_get_delegation" : (Address, SessionKey, Timestamp) -> (GetDelegationResponse) query;
};

One option would be to schedule a code walkthrough.

Very cool use case to see canister delegations used.

2 Likes

Thanks. Why doesn’t the second of the three functions already return the delegation? Why is the third function needed? I suppose just so that it can be called repeatedly?

And what information is used to derive the login principal?

1 Like

siwe_login must be an update call to modify canister state.

siwe_get_delegation must be called using a query call to be able to work with canister data certificate.

II uses the same setup. First call to update state, second call to get the certified data.

The delegation seed is derived a salt, the Ethereum address of the caller, and SIWE message URI.

pub fn generate_seed(address: &EthAddress) → Hash {

Unfortunately, I’m a bit strapped for time right now. Unless you have some specific questions, I’m not going to dig through the code…

Very cool project though! :smiley:

1 Like

So can it be considered a version of II where
a) the Eth address takes the role of the anchor
b) there is only one “device” added to it which is the Eth keypair
c) it produces the same principal for all origins
?

I have a further question about the “one-to-one correlation between Ethereum addresses and Principals” that you mentioned. That one-to-one map is not public, or?

1 Like

Main remaining issue I would see to make it production ready would be some form of proof of work and captcha check like Internet Identity has to prevent spam that fills up the canister.

1 Like

Interesting project, I will study more about it

1 Like

One important distinction is that every project deploys their own SIWE provider canister. The provider can be configured to allow the delegation to be used only with the canisters you list. Most common setup will be to allow the delegate identity to be used with the provider canister and your app canister.

Yes, within the context of current application, as determined by the app URI. Worth noting is that a malicious canister that wants to replicate the setup and has access to the salt and URI still cannot produce the same identity for the same ETH address since the delegation is signed by the canister.

This I don’t understand.

What is an origin is this context? As mentioned above, one copy of the provider canister is meant to support only one app. Attempting to generate an identity with a call from a frontend running on the “wrong” domain will show up as a big red warning in Metamask.

It presently is. Many apps will need to be able to lookup this information to see if the current caller has an identity generated from SIWE, and in that case, for which address.

In other cases, there is no need. I believe that to be the case with the icrc-1 wallet. Once identity has been generated it is of no importance what ETH address was used to generate the identity.

I might add a configuration option to turn the mapping on and off. Projects that have specific needs around this probably want to just fork the provider canister code and modify as they see fit.

One might say that the signing of the message is sort of a proof of work. It can of course be scripted though. You can create a script that creates an infinite number of wallets and log into the canister until it runs out of storage.

Rate limiting in the provider canister would be a less clunky solution than forcing users to solve a captcha every time they login. I am not sure to what extent this can be done in a canister though, I don’t believe you have access to enough information on the caller, IP address for instance.

In analogy to II, you could enhance your provider canister in a way that I can add additional authentication methods to my identity which is derived from my ETH address. Once the link between ETH address and its derived principal is established I could add other authentication methods such as passkeys or devices to it. I would have to sign off on the (first) addition with Ethereum key with a special sign-with-ethereum message. In that sense the analogy with II is that the ETH address is the anchor and the corresponding ETH private key is the first “device” added to it. But there could theoretically be more devices/passkeys added to it as well.

1 Like

By origin I meant domain or URL where the frontend is hosted. But anyway, since you explained that it is meant to be used for only one app the question is answered.

What exactly triggers the “big red warning in Metamask”? Is it when the domain field in the SIWE message does not match the source from which Metamask was called?

I’m trying to implement this in an exisiting project but I keep getting a Invalid actor or address error.

<ReactQueryProvider>  // wrapped QueryClientProvider
	<WagmiProvider config={config}>
		<Connect2ICProvider client={client}>
		    <SiweIdentityProvider idlFactory={siweIdlFactory} canisterId={siweCanisterId.toString()}>
               // other providers and app
            </SiweIdentityProvider>
	    </Connect2ICProvider>
	</WagmiProvider>
</ReactQueryProvider>

on page load i’m doing

const { isConnected, address } = useAccount();
const { login, prepareLoginStatus, prepareLogin } = useSiweIdentity();

	useEffect(() => {
		if (prepareLoginStatus !== 'idle' || !isConnected || !address) return;
		prepareLogin();
	}, [isConnected, address, prepareLogin, prepareLoginStatus]);

for the login i’m doing this

const login = async () => {
    await connector.connect(); // useConnect() from wagmi
    login(); // useSiweIdentity()
}

So i can login with metamask for example and get {accounts: [0x..], chainId: 1} as a response but the login from SIWE doesnt seem to work somehow.

canister is live and deployed, also tried the one from the demo jslsm-aiaaa-aaaal-qdbpa-cai

Do you get that on the login() call?

Do you verify that prepareLoginStatus is success before you proceed?

1 Like

Yes i get that on the login() which looks like it triggers the prepareLogin() (i initially thought the useEffect() was throwing the error.

regarding the prepareLoginStatus, it seems that the state never changes when logging in with wagmi. (it stays idle)

I also reached out to you on TG. maybe thats a bit easier / quicker for debugging this. Would post the result here afterwards ofcouse :slight_smile:

also, i can safely assume that packages like ic-use-actor from the example repo are optional?