We are developing a mobile app using Expo (React Native). Since the @mysten/enoki package throws an error in React Native, we want to use the Enoki HTTP API instead. However, I received an error during the /v1/zklogin/nonce request.
I sent the ephemeral key as Base64 using the following code:
export function makeEphemeralPublicKey() {
const kp = Ed25519Keypair.generate();
const publicKey = kp.getPublicKey().toBase64();
return publicKey;
}
But I received this error:
{“errors”:[{“code”:“custom”,“message”:“Public key is not a valid Ed25519 or Secp256r1 public key”,“path”:[“ephemeralPublicKey”]}]}
For reference, an example ephemeralPublicKey returned by the makeEphemeralPublicKe function is:
uBkVkSG71lcWXiCJO5/+0NzPdyLu9yYubzckl42I/kA=
How can I fix this error?
Can you post an example of the public key you post to the api?
Two common catches are:
-
accidentally sending Sui’s flag-prefixed public key (aka “SuiPublicKey”, 33 bytes), or
-
sending something that isn’t exactly 32 bytes after base64-decoding.
Here’s an example ephemeralPublicKey I send to the API:
zZQ6r425fk/bgxz3kTjtlOQ67cMzzZTZv0szIIlzjVc=
This key is created by generating a new Ed25519 keypair and converting the public key to Base64:
const kp = Ed25519Keypair.generate();
const publicKey = kp.getPublicKey().toBase64();
Thanks for the sample. The issue seems to be that getPublicKey().toBase64() returns Sui’s flag-prefixed public key (“SuiPublicKey”), while Enoki expects the raw public key bytes.
Try the following and send the raw 32-byte key, base64-encoded:
import { toB64 } from ‘@mysten/bcs’;
import { Ed25519Keypair } from ‘@mysten/sui.js/cryptography’;
const kp = Ed25519Keypair.generate();
// ❌ Sui format (flag + key): kp.getPublicKey().toBase64()
// ✅ Raw 32B:
const ephemPkB64 = toB64(kp.getPublicKey().toRawBytes());
const res = await fetch(‘https://api.enoki.mystenlabs.com/v1/zklogin/nonce’, {
method: ‘POST’,
headers: {
‘Content-Type’: ‘application/json’,
‘Authorization’: Bearer ${ENOKI_API_KEY}, // if required
},
body: JSON.stringify({
network: ‘testnet’, // optional
ephemeralPublicKey: ephemPkB64, // raw key in base64
}),
});
To make sure you send the correct format base64-decode before sending and ensure length === 32 for Ed25519
Thanks for the feedback.
toRawBytes() didn’t work for me for some reason, but when I changed it as below, it worked:
const publicKey = toB64(kp.getPublicKey().toSuiBytes());
I’m developing a dApp on mobile. I realized that passing the nonce to googleSignin is a bit problematic compared to the web app. So I tried using the zkLogin HTTP request instead:
fetch("https://api.enoki.mystenlabs.com/v1/zklogin", {
method: "GET",
headers: {
"zklogin-jwt": "string",
"Authorization": "Bearer "
}
})
This returns:
{
"data": {
"salt": "string",
"address": "string",
"publicKey": "string"
}
}
The issue is that, when I want to request a zero-knowledge proof, I’m providing randomness but not requesting a salt. How should I handle creating the zero-knowledge proof in this case?
const body = JSON.stringify({
"ephemeralPublicKey": "string",
"maxEpoch": 0,
"randomness": "string"
})
fetch("https://api.enoki.mystenlabs.com/v1/zklogin/zkp", {
method: "POST",
headers: {
"Content-Type": "application/json",
"zklogin-jwt": "string",
"Authorization": "Bearer "
},
body
})
Let me write the flow step by step, it might help clarify that part. So,
- Create a nonce (Enoki server)
• You send your raw 32-byte Ed25519 ephemeral public key (base64).
• Enoki returns { nonce, randomness, epoch, maxEpoch }.
curl -X POST https://api.enoki.mystenlabs.com/v1/zklogin/nonce \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <YOUR_ENOKI_API_KEY>' \
-d '{"ephemeralPublicKey":"<base64 32B>"}'
- Do OAuth with your provider
- Pass the nonce from step 1 into the provider’s login.
- You receive a JWT from the provider.
- (Optional) Get salt/address (Enoki server)
- Useful to confirm the derived Sui address before proving
curl -X GET https://api.enoki.mystenlabs.com/v1/zklogin \
-H 'Authorization: Bearer <YOUR_ENOKI_API_KEY>' \
-H 'zklogin-jwt: <OIDC_JWT_FROM_PROVIDER>'
- Request the ZK proof (Enoki server)
- You do not send salt here.
- Send the same ephemeralPublicKey, the maxEpoch, and the exact randomness from step 1, plus the JWT in a header.
curl -X POST https://api.enoki.mystenlabs.com/v1/zklogin/zkp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <YOUR_ENOKI_API_KEY>' \
-H 'zklogin-jwt: <OIDC_JWT_FROM_PROVIDER>' \
-d '{
"ephemeralPublicKey":"<base64 32B>",
"maxEpoch": <from step 1>,
"randomness":"<from step 1>"
}'
Let me know if something does not work