Skip to content
Dylan Sharhon edited this page Apr 10, 2020 · 2 revisions

Cheatsheet

There are some quirks and important undocumented APIs that people keep stumbling on. Examples use hexadecimal string i/o and the Bitcoin-compatible secp256k1 curve (others are similar...but different)

Instantiation shortcut

ec = elliptic.ec('secp256k1')

Calculate a private key's 33-byte compressed public key

compressed = true
pub = ec.keyFromPrivate(pri, 'hex').getPublic(compressed, 'hex')

Hash a UTF-8 string or hex using the hash.js library

// utf8 input
msg = hash.sha256().update(string).digest('hex')

// hex input
msg = hash.sha256().update(hex, 'hex').digest('hex')

Generate a Bitcoin-compatible DER-encoded signature with canonical (aka normalized) S values

canonical = { canonical: true }
der = ec.sign(msg, pri, 'hex', canonical).toDER('hex')

Verify a DER-encoded signature

bool = ec.verify(msg, der, pub, 'hex')

Generate a recoverable signature in libsecp256k1 65-byte { R | S | index } format

canonical = { canonical: true }
array = elliptic.utils.toArray(msg, 'hex') // HASHED MSG MUST BE BYTE ARRAY
sig_obj = ec.sign(array, pri, 'hex', canonical)
r = sig_obj.r.toString('hex', 32) // MUST SPECIFY 32 BYTES TO KEEP LEADING ZEROS
s = sig_obj.s.toString('hex', 32)
i = sig_obj.recoveryParam.toString(16).padStart(2, '0')
sig = r + s + i

Recover the compressed public key from a 65-byte recoverable signature

compressed = true
sig_obj = { r: sig.slice(0, 64), s: sig.slice(64, 128) }
recoveryParam = parseInt(sig.slice(128, 130), 16)
array = elliptic.utils.toArray(msg, 'hex') // HASHED MSG MUST BE BYTE ARRAY
pub = ec.recoverPubKey(array, sig_obj, recoveryParam, 'hex').encode('hex', compressed)
Clone this wiki locally