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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"@flowindex/evm-wallet": "^0.1.0",
"@flowindex/flow-passkey": "latest",
"@flowindex/flow-signer": "latest",
"@noble/curves": "^2.0.1",
"@noble/hashes": "^2.0.1",
"@onflow/fcl": "^1.21.9",
"@onflow/rlp": "^1.2.4",
"@radix-ui/react-avatar": "^1.1.11",
Expand Down
14 changes: 12 additions & 2 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ async function applyUrlParams(store: Record<string, any>, setStore: (s: Record<s
const seed = params.get('seed');
const network = params.get('network');
const autoSign = params.get('autoSign');
const address = params.get('address');
const rpc = params.get('rpc');

if (!seed && !network && autoSign == null) return false;
if (!seed && !network && autoSign == null && !rpc) return false;

let updated = { ...store };

Expand All @@ -52,6 +54,14 @@ async function applyUrlParams(store: Record<string, any>, setStore: (s: Record<s
updated.rpcUrl = getDefaultRpc(network);
}

// Apply custom RPC (overrides network default)
if (rpc) {
updated.rpcUrl = rpc;
}

// Reconfigure FCL now so address lookups and key derivation use the right network/RPC
fclConfig(updated.network, updated.rpcUrl);

// Apply autoSign — mark session so popups know it was activated via URL
if (autoSign === 'true') {
updated.autoSign = true;
Expand All @@ -76,7 +86,7 @@ async function applyUrlParams(store: Record<string, any>, setStore: (s: Record<s
// Try to find associated on-chain address
try {
const { findAddressWithPK } = await import("../utils/findAddressWithPK");
const accounts = await findAddressWithPK(seed);
const accounts = await findAddressWithPK(seed, address || undefined);
if (accounts && accounts.length > 0) {
const acct = accounts[0];
updated.address = acct.address;
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
Expand Down
50 changes: 35 additions & 15 deletions utils/sign.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { signFlowTransaction } from "@flowindex/flow-passkey";
import { LocalSigner } from "@flowindex/flow-signer";
import { HASH_ALGO, KEY_TYPE, SIGN_ALGO } from "./constants";
import { KEY_TYPE } from "./constants";

interface SignStore {
id?: string;
Expand Down Expand Up @@ -36,6 +35,37 @@ const domainTag = (tag: string): string => {
).toString("hex")
}

/**
* Sign a hex message with a raw private key using ECDSA.
* Uses @noble/hashes for hashing and @noble/curves for signing —
* both are pure JS with no Web Crypto or network dependency.
* Supports ECDSA_P256 and ECDSA_secp256k1 with SHA2_256 or SHA3_256.
*/
async function signEcdsa(pk: string, message: string, sigAlgo: string, hashAlgo: string): Promise<string> {
const msgBytes = Buffer.from(message, "hex");

let hashBytes: Uint8Array;
if (hashAlgo === "SHA3_256") {
const { sha3_256 } = await import("@noble/hashes/sha3.js");
hashBytes = sha3_256(msgBytes);
} else {
const { sha256 } = await import("@noble/hashes/sha2.js");
hashBytes = sha256(msgBytes);
}

const pkBytes = Uint8Array.from(Buffer.from(pk, "hex"));
let sigBytes: Uint8Array;
if (sigAlgo === "ECDSA_secp256k1") {
const { secp256k1 } = await import("@noble/curves/secp256k1.js");
sigBytes = secp256k1.sign(hashBytes, pkBytes);
} else {
const { p256 } = await import("@noble/curves/nist.js");
sigBytes = p256.sign(hashBytes, pkBytes);
}
// sign() returns compact r||s (64 bytes) — convert directly to hex
return Buffer.from(sigBytes).toString("hex");
}

/**
* Sign a hex-encoded message. Returns { signature, extensionData? }.
* - Passkey: returns extensionData for FLIP-264
Expand All @@ -47,20 +77,10 @@ const signWithKey = async (store: SignStore, message: string): Promise<SignResul
return await signWithPassKey(store, message);
}

// Local key signing via flow-signer LocalSigner
// Local key signing — pure ECDSA, no network calls.
const { signAlgo, hashAlgo, pk } = store.keyInfo;
const signerConfig = {
flowindexUrl: process.env.flowindexUrl || "https://flowindex.io",
network: (process.env.network || "mainnet") as "mainnet" | "testnet",
};
const signer = new LocalSigner(signerConfig, {
privateKey: pk,
sigAlgo: (signAlgo || "ECDSA_P256") as any,
hashAlgo: (hashAlgo || "SHA2_256") as any,
});
await signer.init();
const result = await signer.signFlowTransaction(message);
return { signature: result.signature };
const signature = await signEcdsa(pk, message, signAlgo || "ECDSA_P256", hashAlgo || "SHA2_256");
return { signature };
}

/**
Expand Down