Skip to main content

Overview

The Signature class handles ECDSA (secp256k1) signatures for the Hive blockchain. It provides methods for creating signatures, verifying them, recovering public keys, and converting between different signature formats. Signatures include recovery information that allows the original public key to be recovered from the signature and message, which is essential for Hive’s signature verification system.

Constructor

const signature = new Signature(data, recovery, compressed?)
data
Uint8Array
required
Raw signature data (64 bytes: 32 bytes r-value + 32 bytes s-value)
recovery
number
required
Recovery byte (0-3) used to recover the public key
compressed
boolean
default:"true"
Whether the signature uses compressed format (default: true)

Example

import { Signature } from 'hive-tx'

// Create from raw components
const data = new Uint8Array(64) // 64-byte signature data
const recovery = 0 // Recovery parameter (0-3)
const signature = new Signature(data, recovery, true)

Static Methods

from

Creates a Signature from a hex string.
const signature = Signature.from(hexString)
hexString
string
required
130-character hex string containing recovery byte and signature data
returns
Signature
New Signature instance

Example

import { Signature } from 'hive-tx'

// From hex string (130 characters: 2 for recovery + 128 for signature)
const sigHex = '1f8a7b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f'
const signature = Signature.from(sigHex)

console.log('Recovery:', signature.recovery)
console.log('Data length:', signature.data.length) // 64

Instance Methods

customToString

Returns the signature as a 130-character hex string.
const hexString = signature.customToString()
returns
string
130-character hex string representation of the signature

Example

import { PrivateKey, Transaction } from 'hive-tx'

// Create and sign a transaction
const privateKey = PrivateKey.fromLogin('alice', 'password', 'active')
const tx = new Transaction()
await tx.addOperation('vote', {
  voter: 'alice',
  author: 'bob',
  permlink: 'my-post',
  weight: 10000
})

const { digest } = tx.digest()
const signature = privateKey.sign(digest)

// Convert to hex string
const sigHex = signature.customToString()
console.log('Signature hex:', sigHex)
console.log('Length:', sigHex.length) // 130

toBuffer

Converts the signature to a 65-byte buffer format (recovery byte + signature data).
const buffer = signature.toBuffer()
returns
Uint8Array
65-byte buffer containing recovery byte + 64-byte signature data

Example

import { PrivateKey } from 'hive-tx'
import { sha256 } from '@noble/hashes/sha2'

const privateKey = PrivateKey.fromLogin('alice', 'password', 'active')
const message = sha256('Hello, Hive!')
const signature = privateKey.sign(message)

// Get buffer representation
const buffer = signature.toBuffer()
console.log('Buffer length:', buffer.length) // 65
console.log('Recovery byte:', buffer[0])
console.log('Signature data:', buffer.subarray(1))

getPublicKey

Recovers the public key from this signature and message hash. This is one of the most powerful features of ECDSA signatures on Hive.
const publicKey = signature.getPublicKey(message)
message
Uint8Array | string
required
32-byte message hash as Uint8Array or 64-character hex string (must be a valid SHA256 hash)
returns
PublicKey
Recovered public key that created this signature

Example

import { PrivateKey, Transaction } from 'hive-tx'

// Sign a transaction
const privateKey = PrivateKey.fromLogin('alice', 'password', 'active')
const expectedPublicKey = privateKey.createPublic()

const tx = new Transaction()
await tx.addOperation('transfer', {
  from: 'alice',
  to: 'bob',
  amount: '1.000 HIVE',
  memo: ''
})

const { digest } = tx.digest()
const signature = privateKey.sign(digest)

// Recover public key from signature
const recoveredPublicKey = signature.getPublicKey(digest)

// Verify they match
console.log('Expected:', expectedPublicKey.toString())
console.log('Recovered:', recoveredPublicKey.toString())
console.log('Match:', expectedPublicKey.toString() === recoveredPublicKey.toString())
// Output: true

Creating Signatures

Signatures are typically created using a PrivateKey:
import { PrivateKey } from 'hive-tx'
import { sha256 } from '@noble/hashes/sha2'

const privateKey = PrivateKey.fromLogin('alice', 'password', 'posting')

// Sign a message
const message = sha256('My message to sign')
const signature = privateKey.sign(message)

console.log('Signature:', signature.customToString())
console.log('Recovery:', signature.recovery)

Verifying Signatures

There are two ways to verify signatures:

Method 1: Using PublicKey.verify

import { PrivateKey } from 'hive-tx'
import { sha256 } from '@noble/hashes/sha2'

const privateKey = PrivateKey.fromLogin('alice', 'password', 'active')
const publicKey = privateKey.createPublic()
const message = sha256('Message to verify')
const signature = privateKey.sign(message)

// Verify using PublicKey
const isValid = publicKey.verify(message, signature)
console.log('Signature valid:', isValid) // true

// Verify with wrong message
const wrongMessage = sha256('Different message')
const isInvalid = publicKey.verify(wrongMessage, signature)
console.log('Wrong message:', isInvalid) // false

Method 2: Using Signature.getPublicKey

import { PrivateKey } from 'hive-tx'
import { sha256 } from '@noble/hashes/sha2'

const privateKey = PrivateKey.fromLogin('alice', 'password', 'active')
const expectedPublicKey = privateKey.createPublic()
const message = sha256('Message to verify')
const signature = privateKey.sign(message)

// Recover public key and compare
const recoveredPublicKey = signature.getPublicKey(message)
const isValid = recoveredPublicKey.toString() === expectedPublicKey.toString()
console.log('Signature valid:', isValid) // true

Transaction Signing Example

import { Transaction, PrivateKey, Signature } from 'hive-tx'

async function signAndVerifyTransaction() {
  // Create transaction
  const tx = new Transaction()
  await tx.addOperation('vote', {
    voter: 'alice',
    author: 'bob',
    permlink: 'awesome-post',
    weight: 10000
  })
  
  // Sign with private key
  const privateKey = PrivateKey.fromLogin('alice', 'password', 'posting')
  const signedTx = tx.sign(privateKey)
  
  // Get the signature from the transaction
  const signatureHex = signedTx.signatures[0]
  const signature = Signature.from(signatureHex)
  
  // Verify signature
  const { digest } = tx.digest()
  const publicKey = privateKey.createPublic()
  const isValid = publicKey.verify(digest, signature)
  
  console.log('Transaction signed:', signatureHex)
  console.log('Signature valid:', isValid)
  
  // Broadcast
  const result = await tx.broadcast()
  console.log('Transaction ID:', result.tx_id)
}

signAndVerifyTransaction()

External Signature Application

You can sign externally (e.g., with hardware wallets) and apply the signature:
import { Transaction, PrivateKey, Signature } from 'hive-tx'

async function externalSigning() {
  // Create transaction
  const tx = new Transaction()
  await tx.addOperation('transfer', {
    from: 'alice',
    to: 'bob',
    amount: '5.000 HIVE',
    memo: ''
  })
  
  // Get digest for external signing
  const { digest, txId } = tx.digest()
  console.log('Transaction ID:', txId)
  console.log('Digest to sign:', Buffer.from(digest).toString('hex'))
  
  // Sign externally (simulated here with actual key)
  const privateKey = PrivateKey.fromLogin('alice', 'password', 'active')
  const signature = privateKey.sign(digest)
  const signatureHex = signature.customToString()
  
  console.log('External signature:', signatureHex)
  
  // Apply signature to transaction
  tx.addSignature(signatureHex)
  
  // Verify before broadcasting
  const publicKey = privateKey.createPublic()
  const sig = Signature.from(signatureHex)
  const isValid = publicKey.verify(digest, sig)
  console.log('Signature valid:', isValid)
  
  if (isValid) {
    const result = await tx.broadcast()
    console.log('Broadcast successful:', result.tx_id)
  }
}

externalSigning()

Multi-Signature Transactions

import { Transaction, PrivateKey } from 'hive-tx'

async function multiSigTransaction() {
  const tx = new Transaction()
  await tx.addOperation('transfer', {
    from: 'multisig-account',
    to: 'recipient',
    amount: '100.000 HIVE',
    memo: 'Multi-sig payment'
  })
  
  // Get digest once
  const { digest } = tx.digest()
  
  // Sign with first key
  const key1 = PrivateKey.fromString(process.env.SIGNER1_KEY!)
  const sig1 = key1.sign(digest)
  tx.addSignature(sig1.customToString())
  
  // Sign with second key
  const key2 = PrivateKey.fromString(process.env.SIGNER2_KEY!)
  const sig2 = key2.sign(digest)
  tx.addSignature(sig2.customToString())
  
  // Sign with third key
  const key3 = PrivateKey.fromString(process.env.SIGNER3_KEY!)
  const sig3 = key3.sign(digest)
  tx.addSignature(sig3.customToString())
  
  console.log('Signatures:', tx.transaction?.signatures.length)
  
  // Verify all signatures
  for (let i = 0; i < tx.transaction!.signatures.length; i++) {
    const sig = Signature.from(tx.transaction!.signatures[i])
    const recoveredKey = sig.getPublicKey(digest)
    console.log(`Signer ${i + 1} public key:`, recoveredKey.toString())
  }
  
  // Broadcast
  const result = await tx.broadcast()
  console.log('Multi-sig transaction broadcast:', result.tx_id)
}

multiSigTransaction()

Signature Format

Hive signatures use the following format:
[RECOVERY_BYTE][R_VALUE][S_VALUE]
  • Recovery byte: 1 byte (values 31-34 for compressed, 27-30 for uncompressed)
  • R value: 32 bytes (signature component)
  • S value: 32 bytes (signature component)
  • Total: 65 bytes → 130 hex characters

Format Breakdown

import { PrivateKey } from 'hive-tx'
import { sha256 } from '@noble/hashes/sha2'

const privateKey = PrivateKey.fromLogin('alice', 'password', 'active')
const message = sha256('Example message')
const signature = privateKey.sign(message)

const hex = signature.customToString()
console.log('Full signature:', hex)
console.log('Length:', hex.length) // 130

const buffer = signature.toBuffer()
console.log('Recovery byte:', buffer[0].toString(16)) // Usually 1f-22
console.log('R value:', Buffer.from(buffer.subarray(1, 33)).toString('hex'))
console.log('S value:', Buffer.from(buffer.subarray(33, 65)).toString('hex'))

Properties

data
Uint8Array
64-byte signature data (r-value + s-value)
recovery
number
Recovery parameter (0-3) used to recover the public key

Error Handling

import { Signature } from 'hive-tx'

try {
  // Invalid hex string
  const sig1 = Signature.from('invalid')
} catch (error) {
  console.error('Invalid signature hex:', error.message)
}

try {
  // Wrong message length
  const sig = Signature.from('1f' + '00'.repeat(64))
  const invalidMessage = new Uint8Array(16) // Should be 32 bytes
  sig.getPublicKey(invalidMessage)
} catch (error) {
  console.error('Invalid message:', error.message)
  // Output: Expected a valid sha256 hash as message
}

See Also

  • PrivateKey - Private key management and signing
  • PublicKey - Public key management and verification
  • Transaction - Transaction signing and broadcasting

Build docs developers (and LLMs) love