crypto-tools
A modern suite of cryptography tools, built for the plebian developer.
- All tools are written in typescript and simple to use.
- Methods return a Buff object for quick conversion between formats.
- Library works in both node and the browser.
- Uses the well-audited @noble/curves library for fast ecc operations.
import {
ecdh,
hash,
hd,
keys,
math,
signer,
tree,
Field,
Point
} from '@cmdcode/crypto-tools'
More documentation coming soon!
How to Import
This library is designed to support classic and modern ESM imports, in both a nodejs and browser environment.
Example install via NPM or yarn:
npm install @cmdcode/crypto-tools || yarn add @cmdcode/crypto-tools
Classic import into a nodejs project:
const { Field, Point } = require('@cmdcode/crypto-tools')
Modern import into an nodejs project:
import { Field, Point } from '@cmdcode/crypto-tools'
Classic import into a browser-based project:
<script src="https://unpkg.com/@cmdcode/crypto-tools"></script>
<script>
const { Field, Point } = window.crypto_tools
</script>
Modern import into a browser-based project:
<script type="module">
import { Field, Point } from "https://unpkg.com/@cmdcode/crypto-tools/dist/module.mjs"
</script>
How to Use
This suite is made up of individual tools.
Each tool can be unpacked from the main library, or imported and unpacked directly.
import { signer } from '@cmdcode/crypto-tools'
const { sign_msg, verify_sig } = signer
import { sign_msg, verify_sig } from '@cmdcode/crypto-tools/signer'
Many methods will accept a Bytes
value type, and return a Buff
object. The Bytes
type covers any data type that can be converted into raw bytes.
type Bytes = string | number | bigint | Uint8Array | Buff
Buff
works in place of a standard Uint8Array
and offers a number of quick convertion methods.
const seckey = get_seckey('deadbeef'.repeat(4))
console.log('secret raw :', seckey)
console.log('secret hex :', seckey.hex)
console.log('secret big :', seckey.big)
You can read more about the Buff
API here.
ECDH Tool
Derive a shared secret from between two keys, using Elliptic-Curve Diffe-Hellman protocol.
import {
get_shared_key
get_shared_code
} from '@cmdcode/crypto-tools/ecdh'
Hash Tool
Used for performing SHA-256, SHA-512, HMAC-256, HMAC-512, and other useful hashing methods.
import {
sha256,
sha512,
ripe160,
hash160,
hash256,
hmac256,
hmac512,
taghash,
hash340,
} from '@cmdcode/crypto-tools/hash'
Examples:
const hash = sha256('fda0', 1024n, new Uint8Array(0), 42)
const challenge = hash340('BIP0340/challenge', sig, pubkey, msg)
HD Tool
Generate a pair of keys from a BIP-0032 style derivation path.
import {
derive_key,
parse_tweaks,
generate_code,
encode_extkey
parse_extkey
derive_extkey
} from '@cmdcode/crypto-tools/hd'
Note : The derivation tool also supports using non-numeric characters in the derivation path.
Examples:
import { derive_key, encode_extkey } from '@cmdcode/crypto-tools/hd'
const seed = 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542'
const path = "m/0/2147483647'/1/2147483646'/2"
const key_data = derive_key(path, seed)
console.log('link:', key_data)
link: {
seckey : 'bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23',
pubkey : '024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c',
code : '9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271',
path : "m/0/2147483647'/1/2147483646'/2",
prev : '02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0'
}
const extkey = encode_extkey(keylink)
'extkey : xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j'
KeyPair Tools
Methods for working with key pairs on the secp256k1 curve.
import {
get_seckey,
get_pubkey,
tweak_seckey,
tweak_pubkey,
gen_seckey,
negate_seckey,
gen_keypair,
get_keypair,
convert_32b,
convert_33b,
} from '@cmdcode/crypto-tools/keys'
Examples:
import { gen_seckey, get_pubkey } from '../src/keys.js'
const seckey = gen_seckey()
const pubkey = get_pubkey(seckey, true)
console.log('seckey:', seckey.hex)
console.log('pubkey:', pubkey.hex)
Math Library
A bigint math library for performing arithmetic and field / point operations.
import {
fd,
pt,
mod,
mod_n,
mod_p,
pow,
pow2,
pow_n,
sqrt_n,
sqrt_p,
invert,
mod_bytes,
in_field,
on_curve
} from '@cmdcode/crypto-tools/math'
Proofing Tools
A simplified data proofing system, compatible with Nostr NIP-01 signed events.
import {
create_proof,
parse_proof,
validate_proof,
verify_proof,
create_event,
parse_config
} from '@cmdcode/crypto-tools/proof'
Examples:
const [ seckey, pubkey ] = gen_keypair()
const data = { name : 'bob', key : 'abcd' }
const stamp = Math.floor(Date.now() / 1000)
const params = [[ 'kind', 10000 ], [ 'stamp', stamp ]]
const proof = create_proof(seckey, data, params)
console.log(proof)
`e403621bae6dffd75c76b282cc3525da14bf55a4ee3c396279f35f766b4ad079749b45be02446dc32e4eab02be026eef1592a23c209eb8b571f4f78ac6a762405cc802eae19177096334c0d4e53add9c97dc200e3f2e5700bd17aee14beb01e983456c5cb3461f20c8f3d59e8386a0d8ad4c3d6f58cacb9fc85eef514b8fc1007c476558535de220aebc916b0974e8b76dd584ba14b479d947f9ee52c40333c8?kind=10000&stamp=1694095593`
const is_valid = verify_proof(proof, data)
console.log('is_valid:', is_valid)
`is_valid : true`
const proof_data = parse_proof(proof)
console.log('proof data:', proof_data)
proof data: {
ref: 'e403621bae6dffd75c76b282cc3525da14bf55a4ee3c396279f35f766b4ad079',
pub: '749b45be02446dc32e4eab02be026eef1592a23c209eb8b571f4f78ac6a76240',
pid: '5cc802eae19177096334c0d4e53add9c97dc200e3f2e5700bd17aee14beb01e9',
sig: '83456c5cb3461f20c8f3d59e8386a0d8ad4c3d6f58cacb9fc85eef514b8fc1007c476558535de220aebc916b0974e8b76dd584ba14b479d947f9ee52c40333c8',
params: [ [ 'kind', '10000' ], [ 'stamp', '1694095593' ] ]
}
const event = create_event(proof, data)
console.log('event:', event)
event: {
kind: 10000,
content: '{"name":"bob","key":"abcd"}',
tags: [],
pubkey: '749b45be02446dc32e4eab02be026eef1592a23c209eb8b571f4f78ac6a76240',
id: '5cc802eae19177096334c0d4e53add9c97dc200e3f2e5700bd17aee14beb01e9',
sig: '83456c5cb3461f20c8f3d59e8386a0d8ad4c3d6f58cacb9fc85eef514b8fc1007c476558535de220aebc916b0974e8b76dd584ba14b479d947f9ee52c40333c8',
created_at: 1694095593
}
Signature Tools
A basic signing tool for working with BIP-0340 'schnorr' based digital signatures.
import {
sign_msg,
verify_sig,
gen_nonce,
recover_key
} from '@cmdcode/crypto-tools/signer'
The sign_msg
, verify_msg
, and gen_nonce
methods are configurable through an options object:
export interface SignConfig {
aux ?: Bytes | null
adaptors ?: Bytes[]
nonce_tweaks ?: Bytes[]
recovery_key ?: Bytes
key_tweaks ?: Bytes[]
throws : boolean
xonly : boolean
}
Examples:
import { gen_keypair } from '../src/keys.js'
import { sign_msg, verify_sig } from '../src/sig.js'
const [ seckey, pubkey ] = gen_keypair(true)
const message = 'abcd1234'.repeat(4)
const sig = sign_msg(message, seckey)
const is_valid = verify_sig(sig, message, pubkey)
console.log('signature:', sig.hex)
console.log('is_valid:', is_valid)
Key recovery allows you to designate another key-pair that can recover your private key from a signature, using ECDH.
const [ hot_sec, hot_pub ] = gen_keypair(true)
const [ cold_sec, cold_pub ] = gen_keypair(true)
const message = 'feedcab123'
const sig = sign_msg(message, hot_sec, { recovery_key: cold_pub })
Under the hood, key recovery will modify the nonce generation so that an ECDH shared secret is used instead of your private key.
let sec_nonce = taghash('BIP0340/nonce', seckey, pubkey, message)
let shared_key = ecdh.get_shared_key(seckey, rec_pubkey)
sec_nonce = hash340('BIP0340/nonce', shared_key, pubkey, message)
This allows the 'cold' seckey to compute the shared secret, and thus extract the 'hot' seckey from the signature.
const rec_key = recover_key(sig, message, hot_pub, cold_sec)
console.log('recovered key :', rec_key.hex)
`recovered key : c18d25e25c1b229d14bd97e0daf3e4453765c2e007d9023698458573517ccd55`
console.log('hot secret key :', get_seckey(hot_sec, true).hex)
`hot secret key : c18d25e25c1b229d14bd97e0daf3e4453765c2e007d9023698458573517ccd55`
The formula for recovering a secret key via ECDH shared secret signing:
R_value = sig.slice(0, 32)
s_value = sig.slice(32, 64)
sec_nonce = hash340('BIP0340/nonce', shared_key, pubkey, message)
challenge = hash340('BIP0340/challenge', R_value, pubkey, message)
sec_key = (s_value - sec_nonce) / challenge
Field
The Field
class will convert a key or integer value into a field value under the secp256k1 curve. This field value includes a built-in API for performing math operations in a simple and readable manner.
Each Field
object is stored as raw bytes, and they are directly usable as an Uint8Array
.
Example:
import { Field } from '@cmdcode/crypto-tools/ecc'
const seed = 'dead'.repeat(16)
const tweak = 'beef'.repeat(16)
const secret = Field.mod(seed)
const pubkey = secret.point
const twk_sec = secret.add(tweak)
const twk_pub = twk_sec.point
console.log('original seckey :', secret.hex)
console.log('original pubkey :', pubkey.hex)
console.log('tweaked seckey :', twk_sec.hex)
console.log('tweaked pubkey :', twk_pub.hex)
Documentation for the Field
API:
type FieldValue = string | number | bigint | Uint8Array | Field
class Field extends Uint8Array {
static N: bigint
static mod(x: bigint, n?: bigint): bigint
static normalize(num: FieldValue): Uint8Array
static is_valid(num: bigint): boolean
get buff : Buff
get raw : Uint8Array
get big : bigint
get hex : string
get point : Point
get hasOddY : boolean
get negated : Field
gt (big: FieldValue) : boolean
lt (big: FieldValue) : boolean
eq (big: FieldValue) : boolean
ne (big: FieldValue) : boolean
add (big: FieldValue) : Field
sub (big: FieldValue) : Field
mul (big: FieldValue) : Field
pow (big: FieldValue) : Field
div (big: FieldValue) : Field
negate() : Field
generate() : Point
}
Point
The Point
class will convert a key or integer value into a point value under the secp256k1 curve. This point value includes a built-in API for performing math operations in a simple and readable manner.
Each Point
object contains an x
coordiante and a y
coordinate, stored as bigint values.
Example:
import { Point } from '@cmdcode/crypto-tools/ecc'
const _pubkey = Point.from_x(secret.point.raw)
const _twk_pub = pubkey.add(tweak)
console.log('imported point :', _pubkey)
console.log('imported pubkey :', _pubkey.hex)
console.log('tweaked pubkey :', _twk_pub.hex)
Documentation for the Point
API:
type PointValue = string | number | bigint | Uint8Array | Point
class Point {
static P : bigint
static G : Point
static from_x(x: PointValue, xonly ?: boolean) : Point
static generate(value: FieldValue) : Point
static import(point: Point | ECPoint) : Point
constructor (x: PointValue, y?: bigint)
get x : Buff
get y : Buff
get buff : Buff
get hex : string
get hasEvenY : boolean
get hasOddY : boolean
get negated : Point
eq (value: PointValue) : boolean
add (x: PointValue) : Point
sub (x: PointValue) : Point
mul (value: PointValue) : Point
negate(): Point;
}
A Point
can also be generated from a Field
object, allowing you to perform complex elliptic curve operations using mostly the Field
class.
Here is simplified example of signing and verifing a BIP-0340 digital signature, using the Field
class:
import { Field } from '@cmdcode/crypto-tools/ecc'
import { digest } from '@cmdcode/crypto-tools/hash'
const message = 'dead'.repeat(16)
const secret = 'beef'.repeat(16)
const pubkey = Field.mod(secret).negated.point
const nonce = hash340('BIP0340/nonce', secret, pubkey.x, message)
const R_value = Field.mod(nonce).negated.point
const challenge = hash340('BIP0340/challenge', R_value.x, pubkey.x, message)
const s_value = Field.mod(secret).negated.mul(challenge).add(nonce)
const r_value = Field.mod(s_value).point.sub(pubkey.mul(challenge))
console.log('signature :', R_value.x.hex + s_value.hex)
console.log('is valid :', R_value.hex === r_value.hex)
Development / Testing
This library uses yarn for package management, tape for writing tests, and rollup for cross-platform releases. Here are a few scripts that are useful for development.
yarn bench
yarn build
yarn lint
yarn test
yarn release
Bugs / Issues
If you run into any bugs or have any questions, please submit an issue ticket.
Contribution
Feel free to fork and make contributions. Suggestions are welcome!
License
Use this library however you want!