import { CosmosMetadata, ProofStatus, SignatureProof, } from "@notabene/javascript-sdk"; import { bech32 } from "bech32"; import { StdSignDoc, serializeSignDoc } from "@cosmjs/amino"; import { secp256k1 } from "@noble/curves/secp256k1"; import { ripemd160 } from "@noble/hashes/legacy"; import { sha256 } from "@noble/hashes/sha2"; // Base64 decode function base64ToUint8Array(base64: string): Uint8Array { return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); } // Base64 encode UTF-8 bytes function toBase64Bytes(str: string): string { const utf8 = new TextEncoder().encode(str); let binary = ""; for (let i = 0; i < utf8.length; i++) { binary += String.fromCharCode(utf8[i]); } return btoa(binary); } // Cosmos address derivation (ADR-36) // https://github.com/cosmos/cosmos-sdk/blob/214b11dcbaa129f7b4c0013b2103db9d54b85e9e/docs/architecture/adr-036-arbitrary-signature.md function deriveCosmosAddress(pubkey: Uint8Array, prefix = "cosmos"): string { const sha = sha256(pubkey); const rip = ripemd160(sha); return bech32.encode(prefix, bech32.toWords(rip)); } export async function verifyCosmosSignature( proof: SignatureProof ): Promise { try { const [ns, , address] = proof.address.split(/:/); if (ns !== "cosmos") { return { ...proof, status: ProofStatus.FAILED }; } // Parse the proof JSON const pubKeyBytes = base64ToUint8Array( (proof.chainSpecificData as CosmosMetadata).pub_key.value ); const signatureBytes = base64ToUint8Array(proof.proof); // Step 1: Derive address from pubkey const derivedAddress = deriveCosmosAddress(pubKeyBytes); if (derivedAddress !== address) { return { ...proof, status: ProofStatus.FAILED }; } // Step 2: Build MsgSignData + StdSignDoc const signDoc: StdSignDoc = { chain_id: "", // must match the signing process account_number: "0", sequence: "0", fee: { amount: [], gas: "0" }, msgs: [ { type: "sign/MsgSignData", value: { signer: address, data: toBase64Bytes(proof.attestation), }, }, ], memo: "", }; // Step 3: Serialize + hash const signBytes = await serializeSignDoc(signDoc); const digest = sha256(signBytes); // Step 4: Verify using secp256k1 const verified = secp256k1.verify(signatureBytes, digest, pubKeyBytes); return { ...proof, status: verified ? ProofStatus.VERIFIED : ProofStatus.FAILED, }; } catch { return { ...proof, status: ProofStatus.FAILED }; } }