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
BrandedSignature
Branded Uint8Array type for cryptographic signatures with algorithm metadata.
Type Definition
import type { brand } from 'tevm/brand' ;
type SignatureAlgorithm = "secp256k1" | "p256" | "ed25519" ;
type BrandedSignature = Uint8Array & {
readonly [ brand ] : "Signature" ;
readonly algorithm : SignatureAlgorithm ;
readonly v ?: number ; // Recovery ID for secp256k1 (27 or 28)
};
Properties
brand
readonly [ brand ]: "Signature"
Type brand for runtime type checking using symbol branding.
Visibility: Non-enumerable
Writable: false
Configurable: false
algorithm
readonly algorithm : SignatureAlgorithm
Signature algorithm identifier.
Values:
"secp256k1" - Bitcoin/Ethereum ECDSA
"p256" - NIST P-256 ECDSA
"ed25519" - EdDSA on Curve25519
Visibility: Enumerable
Writable: false
Configurable: false
Recovery ID for secp256k1 signatures (optional).
Values:
27 - First recovery attempt (standard Ethereum)
28 - Second recovery attempt
undefined - Not secp256k1 or no recovery ID
Visibility: Enumerable only if defined
Writable: false
Configurable: false
Byte Structure
ECDSA (secp256k1, p256)
Length: 64 bytes
Layout:
[0-31] r component (32 bytes)
[32-63] s component (32 bytes)
Metadata: algorithm, v (optional)
Ed25519
Length: 64 bytes
Layout:
[0-63] signature (64 bytes)
Metadata: algorithm = 'ed25519'
Properties are defined using Object.defineProperties():
import { brand } from 'tevm/brand' ;
Object . defineProperties ( bytes , {
[brand]: {
value: "Signature" ,
writable: false ,
enumerable: false ,
configurable: false ,
},
algorithm: {
value: "secp256k1" ,
writable: false ,
enumerable: true ,
configurable: false ,
},
v: {
value: 27 ,
writable: false ,
enumerable: v !== undefined ,
configurable: false ,
},
});
Type Guards
function is ( value : unknown ) : value is BrandedSignature
Runtime type guard.
if ( Signature . is ( value )) {
// value is BrandedSignature
console . log ( value . algorithm );
}
Checks:
Is Uint8Array
Has [brand] === "Signature"
Has algorithm property
Algorithm-Specific Checks
function isSecp256k1 ( sig : BrandedSignature ) : boolean {
return sig . algorithm === 'secp256k1' ;
}
function isP256 ( sig : BrandedSignature ) : boolean {
return sig . algorithm === 'p256' ;
}
function isEd25519 ( sig : BrandedSignature ) : boolean {
return sig . algorithm === 'ed25519' ;
}
function isECDSA ( sig : BrandedSignature ) : boolean {
return sig . algorithm === 'secp256k1' || sig . algorithm === 'p256' ;
}
Examples
Creating Branded Signatures
// secp256k1 with recovery ID
const sig1 = Signature . fromSecp256k1 ( r , s , 27 );
console . log ( sig1 . algorithm ); // "secp256k1"
console . log ( sig1 . v ); // 27
console . log ( sig1 [ brand ]); // "Signature"
// P-256 without recovery ID
const sig2 = Signature . fromP256 ( r , s );
console . log ( sig2 . algorithm ); // "p256"
console . log ( sig2 . v ); // undefined
// Ed25519
const sig3 = Signature . fromEd25519 ( sigBytes );
console . log ( sig3 . algorithm ); // "ed25519"
console . log ( sig3 . length ); // 64
const sig = Signature . fromSecp256k1 ( r , s , 27 );
// Algorithm
switch ( sig . algorithm ) {
case 'secp256k1' :
console . log ( 'Bitcoin/Ethereum signature' );
break ;
case 'p256' :
console . log ( 'NIST P-256 signature' );
break ;
case 'ed25519' :
console . log ( 'Ed25519 signature' );
break ;
}
// Recovery ID
if ( sig . v !== undefined ) {
console . log ( `Recovery ID: ${ sig . v } ` );
}
// Brand check
console . log ( sig [ brand ]); // "Signature"
// Metadata preserved through operations
const sig = Signature . fromSecp256k1 ( r , s , 27 );
const normalized = Signature . normalize ( sig );
console . log ( normalized . algorithm ); // "secp256k1"
console . log ( normalized . v ); // 28 (flipped if normalized)
// Metadata lost on byte operations
const bytes = sig . slice (); // Plain Uint8Array
console . log ( bytes . algorithm ); // undefined
Type Safety
function processSignature ( sig : BrandedSignature ) {
// Type-safe access to algorithm
if ( sig . algorithm === 'secp256k1' ) {
// Can safely access v
const recoveryId = sig . v ;
console . log ( `Recovery ID: ${ recoveryId } ` );
}
// Algorithm determines valid operations
if ( sig . algorithm === 'ed25519' ) {
// DER encoding not supported
// const der = Signature.toDER(sig); // Throws error
} else {
// ECDSA supports DER
const der = Signature . toDER ( sig );
}
}
Comparison with Plain Uint8Array
BrandedSignature
const branded = Signature . fromSecp256k1 ( r , s , 27 );
// Advantages:
console . log ( branded . algorithm ); // "secp256k1" (known algorithm)
console . log ( branded . v ); // 27 (known recovery ID)
console . log ( branded . length ); // 64 (guaranteed size)
// Type safety:
function verify ( sig : BrandedSignature ) {
// Compiler knows sig has algorithm property
}
Plain Uint8Array
const plain = Bytes64 ();
// Disadvantages:
console . log ( plain . algorithm ); // undefined (unknown algorithm)
// Need external tracking of algorithm
// Need external tracking of recovery ID
// Need validation of length
// No type safety:
function verify ( sig : Uint8Array ) {
// Need to determine algorithm manually
}
Serialization
JSON
const sig = Signature . fromSecp256k1 ( r , s , 27 );
// Metadata lost in JSON
const json = JSON . stringify ( sig );
// {"0":123,"1":45,...} (array of numbers)
// Need custom serialization for metadata
const serialized = JSON . stringify ({
bytes: Array ( sig ),
algorithm: sig . algorithm ,
v: sig . v ,
});
// Deserialize
const data = JSON . parse ( serialized );
const restored = Signature . fromSecp256k1 (
new Uint8Array ( data . bytes . slice ( 0 , 32 )),
new Uint8Array ( data . bytes . slice ( 32 , 64 )),
data . v
);
Binary
// Raw bytes lose metadata
const sig = Signature . fromSecp256k1 ( r , s , 27 );
const bytes = sig ; // Still BrandedSignature
// Slice creates plain Uint8Array
const plain = sig . slice ();
console . log ( plain . algorithm ); // undefined
// Use toBytes to explicitly strip metadata
const stripped = Signature . toBytes ( sig );
console . log ( stripped . algorithm ); // undefined
Memory Overhead
BrandedSignature has minimal overhead:
Base: 64 bytes (signature data)
Metadata: 3 property descriptors (~48 bytes)
Total: ~112 bytes
Runtime Cost
Property access: O(1) (native object properties)
Type checking: O(1) (simple property checks)
Creation: Minimal overhead vs plain Uint8Array
Optimization
// Efficient: metadata stored in object properties
const sig = Signature . fromSecp256k1 ( r , s , 27 );
console . log ( sig . algorithm ); // Fast property access
// Inefficient: external metadata map
const metadataMap = new Map ();
metadataMap . set ( plainBytes , { algorithm: 'secp256k1' , v: 27 });
Immutability
All properties are readonly:
const sig = Signature . fromSecp256k1 ( r , s , 27 );
// These throw in strict mode:
sig . algorithm = 'p256' ; // Error
sig . v = 28 ; // Error
sig [ brand ] = 'Foo' ; // Error
// Bytes are mutable (Uint8Array behavior):
sig [ 0 ] = 99 ; // Allowed (mutates signature bytes)
// Use toBytes for immutability guarantee:
const bytes = Signature . toBytes ( sig );
Compatibility
Uint8Array Methods
BrandedSignature supports all Uint8Array methods:
const sig = Signature . fromSecp256k1 ( r , s , 27 );
// Array methods work
sig . slice ( 0 , 32 ); // Get r component (plain Uint8Array)
sig . subarray ( 32 , 64 ); // Get s component (plain Uint8Array)
sig . forEach ( byte => console . log ( byte ));
// Note: slice/subarray return plain Uint8Array (lose metadata)
const r = sig . slice ( 0 , 32 );
console . log ( r . algorithm ); // undefined
Type Narrowing
function process ( value : Uint8Array | BrandedSignature ) {
if ( Signature . is ( value )) {
// value is BrandedSignature
console . log ( value . algorithm );
} else {
// value is plain Uint8Array
// Need to determine algorithm externally
}
}
Design Rationale
Why Branded Types?
Type Safety: Compile-time guarantees about signature format
Self-Describing: Algorithm embedded in data
API Simplicity: No need to pass algorithm separately
Runtime Validation: Type guards enable safe operations
Why Not Classes?
// Branded type (current):
const sig : BrandedSignature = Signature . fromSecp256k1 ( r , s , 27 );
sig instanceof Uint8Array ; // true
sig . slice ( 0 , 32 ); // Works
// Class-based alternative:
class SignatureClass {
constructor ( bytes , algorithm , v ) { ... }
}
const sig = new SignatureClass ( bytes , 'secp256k1' , 27 );
sig instanceof Uint8Array ; // false
sig . slice ( 0 , 32 ); // Doesn't work
Branded types maintain Uint8Array compatibility while adding type safety.
See Also