Note that the “raw sign” functionality signs the provided hash directly without any additional byte manipulation. Ensure that your hash includes any required prefixes or suffixes before signing.
Cosmos utilizes the ECDSA signing algorithm with the secp256k1 curve. Below is an implementation example for signing hashes on Cosmos:
Copy
import { Secp256k1, Secp256k1Signature } from "@cosmjs/amino";// Prepare the hash for signingconst hash = '0x6503b027a625549f7be691646404f275f149d17a119a6804b855bac3030037aa';// Obtain the raw signature from Privy's raw_sign endpointconst rawSignature = ... // call privy raw_sign on `hash`// Retrieve the wallet's public key from Privyconst publicKey = ... // the wallet's public key from Privy// Verify the signatureconst signatureBytes = Secp256k1Signature.fromFixedLength( Buffer.from(rawSignature.slice(2), "hex"));const verified = await Secp256k1.verifySignature( signatureBytes, Buffer.from(hash.slice(2), "hex"), Buffer.from(publicKey, "hex"));console.log("Signature valid?", verified); // true
Sui supports multiple cryptographic schemes, with Privy’s implementation utilizing the ed25519 curve and EdDSA signing algorithm. The following example demonstrates transaction signing for Sui:
Copy
import { messageWithIntent, toSerializedSignature,} from "@mysten/sui/cryptography";import { blake2b } from "@noble/hashes/blake2b";import { verifyTransactionSignature } from "@mysten/sui/verify";// After you've added the data to your transactionconst txBytes = await tx.build({ client });const intentMessage = messageWithIntent("TransactionData", txBytes);const digest = blake2b(intentMessage, { dkLen: 32 });// Convert the digest to a hex string for signingconst hashToSign = '0x' + toHex(digest);// Obtain the raw signature from Privy's raw_sign endpointconst rawSignature = ... // call privy raw_sign on `hashToSign`// Create and verify the transaction signatureconst txSignature = toSerializedSignature({signature: rawSignature,signatureScheme: "ED25519",publicKey,});const signer = await verifyTransactionSignature(txBytes, txSignature, {address});console.log(signer.toSuiAddress() === address); // true
Tron implements the ECDSA signing algorithm using the secp256k1 curve. Privy’s implementation returns 64-byte ECDSA signatures (r || s), while Tron requires 65-byte signatures that include a recovery ID (v) as the final byte.
The recovery ID is essential because a 64-byte signature could correspond to two different addresses/private keys. The 65th byte, which can be either 0x1b or 0x1c (derived from 0 or 1 plus 27, following Ethereum standards), resolves this ambiguity.
The following example demonstrates message signing and verification for Tron:
Copy
import { TronWeb } from "tronweb";import { hashMessage } from "tronweb/utils";// Initialize with the wallet's Tron addressconst address = <the wallet's tron address>;// Determine the recovery ID for signature verificationconst getRecoveryId = async ({ message, rawSignature,}: { message: string; rawSignature: string;}) => { return (await tronWeb.trx.verifyMessageV2(message, rawSignature + '1b')) === address ? '1b' : '1c';};// Initialize TronWebconst tronWeb = new TronWeb({ fullHost: "xxx",});// Prepare and sign the messageconst message = "Hello world";const hash = hashMessage(message);// Obtain the raw signature from Privy's raw_sign endpointconst rawSignature = ... // call privy raw_sign on `hash`// Verify the signature with the recovery IDconst signerAddress = await tronWeb.trx.verifyMessageV2( message, signature + (await getRecoveryId({message, rawSignature}));console.log(signerAddress === address); // true