Files
snort/packages/nostr/src/keypair.ts
2023-02-28 21:23:24 +01:00

80 lines
1.9 KiB
TypeScript

import { bech32 } from "bech32"
import { ProtocolError } from "./error"
import * as secp from "@noble/secp256k1"
/**
* A 32-byte secp256k1 public key.
*/
export class PublicKey {
#hex: string
/**
* Expects the key encoded as an npub-prefixed bech32 string, hex string, or byte buffer.
*/
constructor(key: string | Uint8Array) {
this.#hex = parseKey(key, "npub1")
if (this.#hex.length !== 64) {
throw new ProtocolError(`invalid pubkey: ${key}`)
}
}
toString(): string {
return this.#hex
}
}
/**
* A 32-byte secp256k1 private key.
*/
export class PrivateKey {
#hex: string
/**
* Expects the key encoded as an nsec-prefixed bech32 string, hex string, or byte buffer.
*/
constructor(key: string | Uint8Array) {
this.#hex = parseKey(key, "nsec1")
if (this.#hex.length !== 64) {
throw new ProtocolError(`invalid private key: ${this.#hex}`)
}
}
get pubkey(): PublicKey {
return new PublicKey(secp.schnorr.getPublicKey(this.#hex))
}
/**
* The hex representation of the private key. Use with caution!
*/
hexDangerous(): string {
return this.#hex
}
}
/**
* Parse a key into its hex representation.
*/
function parseKey(key: string | Uint8Array, bechPrefix: string): string {
if (typeof key === "string") {
// Is the key encoded in bech32?
if (key.startsWith(bechPrefix)) {
const { words } = bech32.decode(key)
const bytes = Uint8Array.from(bech32.fromWords(words))
return secp.utils.bytesToHex(bytes).toLowerCase()
}
// If not, it must be lowercase hex.
const valid = "0123456789abcdef"
if (key.length % 2 != 0) {
throw new ProtocolError(`invalid lowercase hex string: ${key}`)
}
for (const c of key) {
if (!valid.includes(c)) {
throw new ProtocolError(`invalid lowercase hex string: ${key}`)
}
}
return key
} else {
return secp.utils.bytesToHex(key).toLowerCase()
}
}