mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-11 17:48:50 +00:00
161 lines
5.0 KiB
TypeScript
161 lines
5.0 KiB
TypeScript
import * as secp256k1 from '@noble/secp256k1'
|
|
import {sha256} from '@noble/hashes/sha256'
|
|
import {mnemonicToSeedSync} from '@scure/bip39'
|
|
import {HARDENED_OFFSET, HDKey} from '@scure/bip32'
|
|
|
|
import {getPublicKey} from './keys'
|
|
import {Event, getEventHash, Kind, signEvent, verifySignature} from './event'
|
|
|
|
const MaxKeys = 256
|
|
|
|
function getRootFromMnemonic(mnemonic: string): HDKey {
|
|
return HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic)).derive(
|
|
`m/44'/1237'/41'`
|
|
)
|
|
}
|
|
|
|
export function getPrivateKeyAtIndex(
|
|
mnemonic: string,
|
|
targetIdx: number
|
|
): string {
|
|
let root = getRootFromMnemonic(mnemonic)
|
|
let rootPrivateKey = secp256k1.utils.bytesToHex(root.privateKey as Uint8Array)
|
|
let currentPrivateKey = rootPrivateKey
|
|
|
|
for (let idx = 1; idx <= targetIdx; idx++) {
|
|
let hiddenPrivateKey = secp256k1.utils.bytesToHex(
|
|
root.deriveChild(idx + HARDENED_OFFSET).privateKey as Uint8Array
|
|
)
|
|
currentPrivateKey = getChildPrivateKey(currentPrivateKey, hiddenPrivateKey)
|
|
}
|
|
|
|
return currentPrivateKey
|
|
}
|
|
|
|
export function getPublicKeyAtIndex(
|
|
root: HDKey,
|
|
targetIdx: number
|
|
): Uint8Array {
|
|
let rootPublicKey = root.publicKey as Uint8Array
|
|
|
|
let currentPublicKey = rootPublicKey
|
|
for (let idx = 1; idx <= targetIdx; idx++) {
|
|
let hiddenPublicKey = root.deriveChild(idx + HARDENED_OFFSET)
|
|
.publicKey as Uint8Array
|
|
currentPublicKey = getChildPublicKey(currentPublicKey, hiddenPublicKey)
|
|
}
|
|
|
|
return currentPublicKey
|
|
}
|
|
|
|
function getIndexOfPublicKey(root: HDKey, publicKey: string): number {
|
|
let rootPublicKey = root.publicKey as Uint8Array
|
|
if (secp256k1.utils.bytesToHex(rootPublicKey).slice(2) === publicKey) return 0
|
|
|
|
let currentPublicKey = rootPublicKey
|
|
for (let idx = 1; idx <= MaxKeys; idx++) {
|
|
let hiddenPublicKey = root.deriveChild(idx + HARDENED_OFFSET)
|
|
.publicKey as Uint8Array
|
|
let pubkeyAtIndex = getChildPublicKey(currentPublicKey, hiddenPublicKey)
|
|
if (secp256k1.utils.bytesToHex(pubkeyAtIndex).slice(2) === publicKey)
|
|
return idx
|
|
|
|
currentPublicKey = pubkeyAtIndex
|
|
}
|
|
|
|
throw new Error(
|
|
`public key ${publicKey} not in the set of the first ${MaxKeys} public keys`
|
|
)
|
|
}
|
|
|
|
export function getChildPublicKey(
|
|
parentPublicKey: Uint8Array,
|
|
hiddenPublicKey: Uint8Array
|
|
): Uint8Array {
|
|
if (parentPublicKey.length !== 33 || hiddenPublicKey.length !== 33)
|
|
throw new Error(
|
|
'getChildPublicKey() requires public keys with the leading differentiator byte.'
|
|
)
|
|
|
|
let hash = sha256(
|
|
secp256k1.utils.concatBytes(hiddenPublicKey, parentPublicKey)
|
|
)
|
|
let hashPoint = secp256k1.Point.fromPrivateKey(hash)
|
|
let point = secp256k1.Point.fromHex(hiddenPublicKey).add(hashPoint)
|
|
return point.toRawBytes(true)
|
|
}
|
|
|
|
export function getChildPrivateKey(
|
|
parentPrivateKey: string,
|
|
hiddenPrivateKey: string
|
|
): string {
|
|
let parentPublicKey = secp256k1.getPublicKey(parentPrivateKey, true)
|
|
let hiddenPublicKey = secp256k1.getPublicKey(hiddenPrivateKey, true)
|
|
let hash = sha256(
|
|
secp256k1.utils.concatBytes(hiddenPublicKey, parentPublicKey)
|
|
)
|
|
let hashScalar = BigInt(`0x${secp256k1.utils.bytesToHex(hash)}`)
|
|
let hiddenPrivateKeyScalar = BigInt(`0x${hiddenPrivateKey}`)
|
|
let sumScalar = hiddenPrivateKeyScalar + hashScalar
|
|
let modulo = secp256k1.utils.mod(sumScalar, secp256k1.CURVE.n)
|
|
return modulo.toString(16).padStart(64, '0')
|
|
}
|
|
|
|
export function buildRevocationEvent(
|
|
mnemonic: string,
|
|
compromisedKey: string,
|
|
content = ''
|
|
): {
|
|
parentPrivateKey: string
|
|
event: Event
|
|
} {
|
|
let root = getRootFromMnemonic(mnemonic)
|
|
let idx = getIndexOfPublicKey(root, compromisedKey)
|
|
let hiddenKey = secp256k1.utils.bytesToHex(
|
|
root.deriveChild(idx + HARDENED_OFFSET).publicKey as Uint8Array
|
|
)
|
|
let parentPrivateKey = getPrivateKeyAtIndex(mnemonic, idx - 1)
|
|
let parentPublicKey = getPublicKey(parentPrivateKey)
|
|
|
|
let event: Event = {
|
|
kind: 13,
|
|
tags: [
|
|
['p', compromisedKey],
|
|
['hidden-key', hiddenKey]
|
|
],
|
|
created_at: Math.round(Date.now() / 1000),
|
|
content,
|
|
pubkey: parentPublicKey
|
|
}
|
|
|
|
event.sig = signEvent(event, parentPrivateKey)
|
|
event.id = getEventHash(event)
|
|
|
|
return {parentPrivateKey, event}
|
|
}
|
|
|
|
export function validateRevocation(event: Event): boolean {
|
|
if (event.kind !== Kind.StatelessRevocation) return false
|
|
if (!verifySignature(event)) return false
|
|
|
|
let invalidKeyTag = event.tags.find(([t, v]) => t === 'p' && v)
|
|
if (!invalidKeyTag) return false
|
|
let invalidKey = invalidKeyTag[1]
|
|
|
|
let hiddenKeyTag = event.tags.find(([t, v]) => t === 'hidden-key' && v)
|
|
if (!hiddenKeyTag) return false
|
|
let hiddenKey = secp256k1.utils.hexToBytes(hiddenKeyTag[1])
|
|
if (hiddenKey.length !== 33) return false
|
|
|
|
let currentKeyAlt1 = secp256k1.utils.hexToBytes('02' + event.pubkey)
|
|
let currentKeyAlt2 = secp256k1.utils.hexToBytes('03' + event.pubkey)
|
|
let childKeyAlt1 = secp256k1.utils
|
|
.bytesToHex(getChildPublicKey(currentKeyAlt1, hiddenKey))
|
|
.slice(2)
|
|
let childKeyAlt2 = secp256k1.utils
|
|
.bytesToHex(getChildPublicKey(currentKeyAlt2, hiddenKey))
|
|
.slice(2)
|
|
|
|
return childKeyAlt1 === invalidKey || childKeyAlt2 === invalidKey
|
|
}
|