Documentation Index Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Try it Live Run Signature examples in the interactive playground
Signature Formats
Comprehensive comparison of signature encoding formats: compact, DER, and RSV.
Format Size Components Use Case Compact 64 bytes r ‖ s Space-efficient, standard ECDSA Compact+V 65 bytes r ‖ s ‖ v Ethereum transactions (with recovery) DER Variable (~70-72 bytes) ASN.1 encoded Bitcoin, X.509, legacy systems RSV Object N/A (object) API structures, JSON EIP-2098 64 bytes r ‖ (s + yParity) Compact with recovery (see EIP-2098 ) RPC N/A (object) hex JSON-RPC responses Tuple N/A (array) [yParity, r, s] Transaction envelope serialization
Structure
Standard (64 bytes):
[0-31] r component (32 bytes, big-endian)
[32-63] s component (32 bytes, big-endian)
With Recovery ID (65 bytes):
[0-31] r component (32 bytes)
[32-63] s component (32 bytes)
[64] v recovery ID (1 byte: 27 or 28)
Usage
import { Signature } from 'tevm' ;
// Create from compact (64 bytes)
const compact64 = Bytes64 ();
compact64 . set ( rBytes , 0 );
compact64 . set ( sBytes , 32 );
const sig1 = Signature . fromCompact ( compact64 , 'secp256k1' );
// Create from compact with v (65 bytes)
const compact65 = new Uint8Array ( 65 );
compact65 . set ( rBytes , 0 );
compact65 . set ( sBytes , 32 );
compact65 [ 64 ] = 27 ;
const sig2 = Signature . fromCompact ( compact65 , 'secp256k1' );
// Convert to compact
const output = Signature . toCompact ( sig2 );
console . log ( output . length ); // 65 (includes v if present)
Advantages
✅ Fixed size (predictable memory)
✅ Simple structure
✅ Efficient for storage and transmission
✅ Standard ECDSA format
Disadvantages
❌ No self-describing (need external algorithm info)
❌ No built-in validation
❌ Recovery ID optional (separate byte)
Use Cases
Ethereum transactions
Blockchain signatures
High-performance applications
Fixed-size storage requirements
Structure
SEQUENCE {
INTEGER r
INTEGER s
}
Example (hex):
30 45 -- SEQUENCE tag + length (69 bytes)
02 21 -- INTEGER tag + length (33 bytes)
00 ab cd ef... -- r value (padded if high bit set)
02 20 -- INTEGER tag + length (32 bytes)
12 34 56... -- s value
Encoding Rules
Tags:
0x30 - SEQUENCE tag
0x02 - INTEGER tag
Length Encoding:
Short form: 0x00-0x7F (length fits in 1 byte)
Long form: 0x80 + n followed by n-byte length
Integer Padding:
Leading 0x00 added if high bit set (to indicate positive number)
Leading zeros stripped (minimal encoding)
Usage
import { Signature } from 'tevm' ;
// Create from DER
const derBytes = Hex . toBytes ( '0x3045...' );
const sig = Signature . fromDER ( derBytes , 'secp256k1' , 27 );
// Convert to DER
const der = Signature . toDER ( sig );
// DER structure validation
function parseDER ( der : Uint8Array ) {
let pos = 0 ;
// Parse SEQUENCE
if ( der [ pos ++ ] !== 0x30 ) throw new Error ( 'Expected SEQUENCE' );
const seqLen = der [ pos ++ ];
// Parse r
if ( der [ pos ++ ] !== 0x02 ) throw new Error ( 'Expected INTEGER' );
const rLen = der [ pos ++ ];
const r = der . slice ( pos , pos + rLen );
pos += rLen ;
// Parse s
if ( der [ pos ++ ] !== 0x02 ) throw new Error ( 'Expected INTEGER' );
const sLen = der [ pos ++ ];
const s = der . slice ( pos , pos + sLen );
return { r , s };
}
Advantages
✅ Self-describing format
✅ Standard ASN.1 encoding
✅ Widely supported (Bitcoin, TLS, X.509)
✅ Built-in structure validation
Disadvantages
❌ Variable size (70-72 bytes typical)
❌ Parsing overhead
❌ More complex than compact
❌ Larger than necessary
Use Cases
Bitcoin transactions (legacy)
X.509 certificates
TLS connections
Legacy cryptographic systems
Structure
interface RSVSignature {
r : Uint8Array ; // 32 bytes
s : Uint8Array ; // 32 bytes
v ?: number ; // Recovery ID (27 or 28)
}
Usage
import { Signature } from 'tevm' ;
// Create from RSV object
const sig = Signature ({
r: rBytes ,
s: sBytes ,
v: 27 ,
algorithm: 'secp256k1'
});
// Extract components
const r = Signature . getR ( sig );
const s = Signature . getS ( sig );
const v = Signature . getV ( sig );
// Reconstruct object
const rsv = {
r: Signature . getR ( sig ),
s: Signature . getS ( sig ),
v: Signature . getV ( sig )
};
Advantages
✅ Human-readable structure
✅ Type-safe (with TypeScript)
✅ Easy to work with in code
✅ JSON serializable
Disadvantages
❌ Not a binary format
❌ Overhead for serialization
❌ No standard wire format
❌ Requires conversion for transmission
Use Cases
API responses (JSON)
Internal data structures
Testing and debugging
Configuration files
Size Comparison
const sig = Signature . fromSecp256k1 ( r , s , 27 );
// Compact (without v)
const compact64 = Signature . toCompact (
Signature . fromSecp256k1 ( r , s )
);
console . log ( compact64 . length ); // 64 bytes
// Compact (with v)
const compact65 = Signature . toCompact ( sig );
console . log ( compact65 . length ); // 65 bytes
// DER (variable)
const der = Signature . toDER ( sig );
console . log ( der . length ); // 70-72 bytes (typical)
// RSV object (not directly comparable - in-memory structure)
const rsv = {
r: Signature . getR ( sig ), // 32 bytes
s: Signature . getS ( sig ), // 32 bytes
v: Signature . getV ( sig ) // number
};
Conversion Matrix
From → To Compact DER RSV Compact Identity toDER()getR/S/V()DER fromDER() → toCompact()Identity fromDER() → getR/S/V()RSV from() → toCompact()from() → toDER()Identity
// Benchmark (approximate times)
const sig = Signature . fromSecp256k1 ( r , s , 27 );
// Compact: Fastest (zero-copy)
console . time ( 'compact' );
const compact = Signature . toCompact ( sig );
console . timeEnd ( 'compact' ); // ~0.001ms
// DER: Slower (encoding overhead)
console . time ( 'der' );
const der = Signature . toDER ( sig );
console . timeEnd ( 'der' ); // ~0.01-0.05ms
// RSV: Fast (direct access)
console . time ( 'rsv' );
const rsv = {
r: Signature . getR ( sig ),
s: Signature . getS ( sig ),
v: Signature . getV ( sig )
};
console . timeEnd ( 'rsv' ); // ~0.001ms
Choose Compact When:
✅ Building Ethereum transactions
✅ Storage space is critical
✅ Fixed-size buffers required
✅ High-performance needs
Choose DER When:
✅ Bitcoin transaction signing
✅ Interop with legacy systems
✅ X.509 certificate operations
✅ Standards compliance required
Choose RSV When:
✅ Building APIs (JSON responses)
✅ Internal data structures
✅ Testing and debugging
✅ Human-readable output needed
Real-World Examples
Ethereum Transaction
// Ethereum uses compact with v
const tx = {
// ...transaction fields
r: '0x1234...' , // 32 bytes (hex)
s: '0x5678...' , // 32 bytes (hex)
v: 27 // Recovery ID
};
// Convert to signature
const sig = Signature ({
r: Hex . toBytes ( tx . r ),
s: Hex . toBytes ( tx . s ),
v: tx . v ,
algorithm: 'secp256k1'
});
// Serialize as compact+v (65 bytes)
const serialized = Signature . toCompact ( sig );
Bitcoin Transaction
// Bitcoin uses DER encoding
const bitcoinSig = derBytes ; // From transaction scriptSig
// Parse DER signature
const sig = Signature . fromDER ( bitcoinSig , 'secp256k1' );
// Convert to compact for processing
const compact = Signature . toCompact ( sig );
JSON API Response
// API returns RSV object
const apiResponse = {
signature: {
r: '0x1234...' ,
s: '0x5678...' ,
v: 27
}
};
// Parse to signature
const sig = Signature ({
r: Hex . toBytes ( apiResponse . signature . r ),
s: Hex . toBytes ( apiResponse . signature . s ),
v: apiResponse . signature . v ,
algorithm: 'secp256k1'
});
Compact Validation
function validateCompact ( bytes : Uint8Array ) : boolean {
// Check length
if ( bytes . length !== 64 && bytes . length !== 65 ) {
return false ;
}
// Check r and s are not zero
const r = bytes . slice ( 0 , 32 );
const s = bytes . slice ( 32 , 64 );
if ( r . every ( b => b === 0 ) || s . every ( b => b === 0 )) {
return false ;
}
// Check v if present
if ( bytes . length === 65 ) {
const v = bytes [ 64 ];
if ( v !== 27 && v !== 28 ) {
return false ; // Could also accept EIP-155 values
}
}
return true ;
}
DER Validation
function validateDER ( der : Uint8Array ) : boolean {
try {
let pos = 0 ;
// SEQUENCE tag
if ( der [ pos ++ ] !== 0x30 ) return false ;
// SEQUENCE length
const seqLen = der [ pos ++ ];
if ( pos + seqLen !== der . length ) return false ;
// r INTEGER
if ( der [ pos ++ ] !== 0x02 ) return false ;
const rLen = der [ pos ++ ];
pos += rLen ;
// s INTEGER
if ( der [ pos ++ ] !== 0x02 ) return false ;
const sLen = der [ pos ++ ];
pos += sLen ;
// Must consume all bytes
return pos === der . length ;
} catch {
return false ;
}
}
Structure
interface RpcSignature {
r : `0x ${ string } ` ; // 32 bytes as hex
s : `0x ${ string } ` ; // 32 bytes as hex
yParity : `0x0` | `0x1` ; // Recovery ID (0 or 1)
v ?: `0x ${ string } ` ; // Optional legacy v (27, 28, or EIP-155)
}
Usage
import * as Signature from 'tevm/primitives/Signature' ;
// Convert to RPC format
const sig = Signature . fromSecp256k1 ( r , s , 27 );
const rpc = Signature . toRpc ( sig );
console . log ( rpc . r ); // "0x1234..."
console . log ( rpc . s ); // "0x5678..."
console . log ( rpc . yParity ); // "0x0"
console . log ( rpc . v ); // "0x1b"
// Parse from RPC format
const parsed = Signature . fromRpc ( rpc );
console . log ( parsed . v ); // 27
Advantages
✅ JSON-serializable (all hex strings)
✅ Standard JSON-RPC format
✅ Includes both yParity and v for compatibility
Use Cases
JSON-RPC responses (eth_getTransactionByHash)
REST API payloads
WebSocket message bodies
Structure
type SignatureTuple = [ yParity : 0 | 1 , r : Uint8Array , s : Uint8Array ];
Usage
import * as Signature from 'tevm/primitives/Signature' ;
// Convert to tuple format
const sig = Signature . fromSecp256k1 ( r , s , 27 );
const [ yParity , rBytes , sBytes ] = Signature . toTuple ( sig );
console . log ( yParity ); // 0
// Create from tuple
const restored = Signature . fromTuple ([ 0 , r , s ]);
console . log ( restored . v ); // 27
// With chainId for EIP-155
const eip155Sig = Signature . fromTuple ([ 1 , r , s ], 1 );
console . log ( eip155Sig . v ); // 38 (chainId*2 + 35 + yParity)
Advantages
✅ Minimal representation
✅ Matches RLP encoding for typed transactions
✅ Easy destructuring
Use Cases
EIP-2718 typed transaction envelopes
RLP serialization
Compact storage
Interoperability
// Compact → DER
function compactToDER ( compact : Uint8Array ) : Uint8Array {
const sig = Signature . fromCompact ( compact , 'secp256k1' );
return Signature . toDER ( sig );
}
// DER → Compact
function derToCompact ( der : Uint8Array , v ?: number ) : Uint8Array {
const sig = Signature . fromDER ( der , 'secp256k1' , v );
return Signature . toCompact ( sig );
}
// RSV → Compact
function rsvToCompact ( rsv : { r : Uint8Array ; s : Uint8Array ; v ?: number }) : Uint8Array {
const sig = Signature . fromSecp256k1 ( rsv . r , rsv . s , rsv . v );
return Signature . toCompact ( sig );
}
// Compact → RSV
function compactToRSV ( compact : Uint8Array ) : { r : Uint8Array ; s : Uint8Array ; v ?: number } {
const sig = Signature . fromCompact ( compact , 'secp256k1' );
return {
r: Signature . getR ( sig ),
s: Signature . getS ( sig ),
v: Signature . getV ( sig )
};
}
See Also