Message Signing
Sign and verify messages using CIP-30 COSE signatures
Message Signing
Evolution SDK implements the CIP-30 message signing standard using COSE (CBOR Object Signing and Encryption). This allows wallets to sign arbitrary data and prove ownership of an address without submitting a transaction.
Common use cases:
- Authentication — Prove you control an address (login with wallet)
- Data attestation — Sign off-chain data with your key
- Message verification — Verify a signature came from a specific address
Sign a Message
Use COSE.SignData.signData to sign arbitrary data with a private key:
import { COSE, PrivateKey, Address } from "@evolution-sdk/evolution"
declare const privateKey: PrivateKey.PrivateKey
declare const myAddress: Address.Address
// Create a payload from text
const payload = COSE.Utils.fromText("Hello, I'm signing this message!")
// Sign with your private key
const signedMessage = COSE.SignData.signData(
Address.toHex(myAddress), // Address as hex
payload, // Payload bytes
privateKey // Your private key
)
// signedMessage contains:
// - signature: Uint8Array (CBOR-encoded COSE_Sign1)
// - key: Uint8Array (CBOR-encoded COSE_Key with public key)Verify a Signature
Use COSE.SignData.verifyData to verify a signed message:
import { COSE, Address, KeyHash } from "@evolution-sdk/evolution"
declare const myAddress: Address.Address
declare const signerKeyHash: KeyHash.KeyHash
declare const signedMessage: COSE.SignData.SignedMessage
// The original payload that was signed
const payload = COSE.Utils.fromText("Hello, I'm signing this message!")
// Verify the signature
const isValid = COSE.SignData.verifyData(
Address.toHex(myAddress), // Expected signer address
KeyHash.toHex(signerKeyHash), // Expected signer key hash
payload, // Original payload
signedMessage // The signed message to verify
)
if (isValid) {
console.log("Signature is valid!")
}Verification checks:
- Payload matches the signed data
- Address in the signature matches the expected address
- Algorithm is EdDSA
- Public key hash matches the expected key hash
- Ed25519 signature is cryptographically valid
Payload Utilities
The COSE.Utils module provides helpers for creating payloads:
import { COSE } from "@evolution-sdk/evolution"
// From text string
const textPayload = COSE.Utils.fromText("Sign this message")
// Back to text
const text = COSE.Utils.toText(textPayload)
// From hex string
const hexPayload = COSE.Utils.fromHex("deadbeef")
// Back to hex
const hex = COSE.Utils.toHex(hexPayload)Low-Level COSE API
For advanced use cases, you can work with COSE structures directly.
COSE_Sign1
The single-signer signature structure:
import { COSE } from "@evolution-sdk/evolution"
// Decode a COSE_Sign1 from CBOR bytes
declare const cborBytes: Uint8Array
const coseSign1 = COSE.Sign1.coseSign1FromCBORBytes(cborBytes)
// Access fields
const payload = coseSign1.payload // Signed payload
const signature = coseSign1.signature // Ed25519 signature
const headers = coseSign1.headers // Protected + unprotected headers
// Encode back to CBOR
const encoded = COSE.Sign1.coseSign1ToCBORBytes(coseSign1)COSE_Key
Represents a public key in COSE format:
import { COSE, PrivateKey, VKey } from "@evolution-sdk/evolution"
import { Schema } from "effect"
declare const privateKey: PrivateKey.PrivateKey
// Build a COSE_Key from a private key
const vkey = VKey.fromPrivateKey(privateKey)
const ed25519Key = new COSE.Key.EdDSA25519Key(
{ privateKey: undefined, publicKey: vkey },
{ disableValidation: true }
)
const coseKey = ed25519Key.build()
// Encode to CBOR
const keyBytes = Schema.encodeSync(COSE.Key.COSEKeyFromCBORBytes())(coseKey)Headers
COSE headers carry metadata about the signature:
import { COSE } from "@evolution-sdk/evolution"
// Create headers
const protectedHeaders = COSE.Header.headerMapNew()
.setAlgorithmId(COSE.Label.AlgorithmId.EdDSA)
const unprotectedHeaders = COSE.Header.headerMapNew()
const headers = COSE.Header.headersNew(protectedHeaders, unprotectedHeaders)How It Works
The CIP-30 signing process follows this flow:
- Protected headers are created with the algorithm (EdDSA) and signer's address
- Unprotected headers mark the payload as not pre-hashed
- A COSE_Sign1 builder creates the
Sig_structure1(the data to be signed) - The private key signs the
Sig_structure1with Ed25519 - The result is CBOR-encoded as a
COSE_Sign1+COSE_Keypair
This matches the api.signData() specification from CIP-30, making it compatible with all CIP-30 compliant wallets.
Next Steps
- Wallets — Wallet types and setup
- Private Key — Working with private keys
- API Wallet — CIP-30 browser wallet integration