-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathschnorr.ts
83 lines (69 loc) · 2.99 KB
/
schnorr.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import * as bitcoin from 'bitcoinjs-lib';
import * as ecc from 'tiny-secp256k1';
import {BIP32Factory, BIP32Interface} from 'bip32';
import {bech32m} from 'bech32';
bitcoin.initEccLib(ecc);
const prefix = "200'";
const bip32 = BIP32Factory(ecc);
interface TweakedKeyPair {
privateKey: Buffer;
publicKey: Buffer;
}
// Finds the tweaked private key from an extended private key matching a given address
export function findTweakedPair(xprv: string, derivedAddress: string): TweakedKeyPair | null {
const path = `${prefix}'/86'/1'/0'/0/*`;
const network = xprv.startsWith('tprv') ? bitcoin.networks.testnet : bitcoin.networks.bitcoin;
const node: BIP32Interface = bip32.fromBase58(xprv, network);
const pathComponents = path.split('/').filter(component => component !== '*');
const baseNode: BIP32Interface = pathComponents.reduce((acc: BIP32Interface, component: string) => {
const isHardened = component.endsWith('\'');
const index = parseInt(component, 10);
return isHardened ? acc.deriveHardened(index) : acc.derive(index);
}, node);
const maxDerivations = 1000;
for (let i = 0; i < maxDerivations; i++) {
const child = baseNode.derive(i);
const pubkey = child.publicKey.slice(1, 33);
const {address} = bitcoin.payments.p2tr({
internalPubkey: pubkey,
network,
});
if (!address) continue;
const tweakHash = bitcoin.crypto.taggedHash('TapTweak', pubkey);
const tweakedPriv = child.tweak(tweakHash) as BIP32Interface;
if (address === derivedAddress || spaceAddress(address) === derivedAddress) {
return {
privateKey: tweakedPriv.privateKey!,
publicKey: tweakedPriv.publicKey!.slice(1),
};
}
}
return null;
}
// Converts ScriptPubKey to an address
export function scriptPubKeyToAddress(scriptPubKey: string, network: string = 'mainnet'): string {
if (!scriptPubKey.startsWith('5120') || scriptPubKey.length !== 68) {
throw new Error('Invalid P2TR ScriptPubKey');
}
const pubkeyHex = scriptPubKey.slice(4);
const pubkeyBytes = Buffer.from(pubkeyHex, 'hex');
const hrp = network === 'mainnet' ? 'bcs' : 'tbs';
const pubkeyBits = bech32m.toWords(pubkeyBytes);
return bech32m.encode(hrp, [1].concat(pubkeyBits));
}
// Adjusts address prefix for the space network
function spaceAddress(address: string): string {
const decoded = bech32m.decode(address);
if (decoded.prefix !== 'tb' && decoded.prefix !== 'bc') {
throw new Error('Invalid address prefix');
}
return bech32m.encode(`${decoded.prefix}s`, decoded.words);
}
// Schnorr signing
export function sign(digest: Buffer | Uint8Array, privateKey: Buffer): Buffer {
return Buffer.from(ecc.signSchnorr(digest, privateKey));
}
// Schnorr verification
export function verify(digest: Buffer | Uint8Array, publicKey: Buffer, signature: Buffer): boolean {
return ecc.verifySchnorr(digest, publicKey, signature);
}