-
-
Notifications
You must be signed in to change notification settings - Fork 49
/
Copy pathutils.ts
129 lines (116 loc) · 5.21 KB
/
utils.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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { schnorr, secp256k1 as secp } from '@noble/curves/secp256k1';
import { ripemd160 } from '@noble/hashes/ripemd160';
import { sha256 } from '@noble/hashes/sha256';
import { utils as packedUtils, U32LE } from 'micro-packed';
export type Hex = string | Uint8Array;
export type Bytes = Uint8Array;
const Point = secp.ProjectivePoint;
const CURVE_ORDER = secp.CURVE.n;
const isBytes: (a: unknown) => a is Uint8Array = packedUtils.isBytes;
const concatBytes: (...arrays: Uint8Array[]) => Uint8Array = packedUtils.concatBytes;
const equalBytes: (a: Uint8Array, b: Uint8Array) => boolean = packedUtils.equalBytes;
export { concatBytes, equalBytes, isBytes, sha256 };
export const hash160 = (msg: Uint8Array): Uint8Array => ripemd160(sha256(msg));
export const sha256x2 = (...msgs: Uint8Array[]): Uint8Array => sha256(sha256(concatBytes(...msgs)));
export const randomPrivateKeyBytes: () => Uint8Array = schnorr.utils.randomPrivateKey;
export const pubSchnorr = schnorr.getPublicKey as (priv: string | Uint8Array) => Uint8Array;
export const pubECDSA: (privateKey: string | Uint8Array, isCompressed?: boolean) => Uint8Array =
secp.getPublicKey;
// low-r signature grinding. Used to reduce tx size by 1 byte.
// noble/secp256k1 does not support the feature: it is not used outside of BTC.
// We implement it manually, because in BTC it's common.
// Not best way, but closest to bitcoin implementation (easier to check)
const hasLowR = (sig: { r: bigint; s: bigint }) => sig.r < CURVE_ORDER / 2n;
export function signECDSA(hash: Bytes, privateKey: Bytes, lowR = false): Bytes {
let sig = secp.sign(hash, privateKey);
if (lowR && !hasLowR(sig)) {
const extraEntropy = new Uint8Array(32);
let counter = 0;
while (!hasLowR(sig)) {
extraEntropy.set(U32LE.encode(counter++));
sig = secp.sign(hash, privateKey, { extraEntropy });
if (counter > 4294967295) throw new Error('lowR counter overflow: report the error');
}
}
return sig.toDERRawBytes();
}
export const signSchnorr: typeof schnorr.sign = schnorr.sign;
export const tagSchnorr: typeof schnorr.utils.taggedHash = schnorr.utils.taggedHash;
export enum PubT {
ecdsa,
schnorr,
}
export function validatePubkey(pub: Bytes, type: PubT): Bytes {
const len = pub.length;
if (type === PubT.ecdsa) {
if (len === 32) throw new Error('Expected non-Schnorr key');
Point.fromHex(pub); // does assertValidity
return pub;
} else if (type === PubT.schnorr) {
if (len !== 32) throw new Error('Expected 32-byte Schnorr key');
schnorr.utils.lift_x(schnorr.utils.bytesToNumberBE(pub));
return pub;
} else {
throw new Error('Unknown key type');
}
}
export function tapTweak(a: Bytes, b: Bytes): bigint {
const u = schnorr.utils;
const t = u.taggedHash('TapTweak', a, b);
const tn = u.bytesToNumberBE(t);
if (tn >= CURVE_ORDER) throw new Error('tweak higher than curve order');
return tn;
}
export function taprootTweakPrivKey(privKey: Bytes, merkleRoot: Bytes = new Uint8Array()): Bytes {
const u = schnorr.utils;
const seckey0 = u.bytesToNumberBE(privKey); // seckey0 = int_from_bytes(seckey0)
const P = Point.fromPrivateKey(seckey0); // P = point_mul(G, seckey0)
// seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0
const seckey = P.hasEvenY() ? seckey0 : u.mod(-seckey0, CURVE_ORDER);
const xP = u.pointToBytes(P);
// t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h)); >= SECP256K1_ORDER check
const t = tapTweak(xP, merkleRoot);
// bytes_from_int((seckey + t) % SECP256K1_ORDER)
return u.numberToBytesBE(u.mod(seckey + t, CURVE_ORDER), 32);
}
export function taprootTweakPubkey(pubKey: Bytes, h: Bytes): [Bytes, number] {
const u = schnorr.utils;
const t = tapTweak(pubKey, h); // t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
const P = u.lift_x(u.bytesToNumberBE(pubKey)); // P = lift_x(int_from_bytes(pubkey))
const Q = P.add(Point.fromPrivateKey(t)); // Q = point_add(P, point_mul(G, t))
const parity = Q.hasEvenY() ? 0 : 1; // 0 if has_even_y(Q) else 1
return [u.pointToBytes(Q), parity]; // bytes_from_int(x(Q))
}
// Another stupid decision, where lack of standard affects security.
// Multisig needs to be generated with some key.
// We are using approach from BIP 341/bitcoinjs-lib: SHA256(uncompressedDER(SECP256K1_GENERATOR_POINT))
// It is possible to switch SECP256K1_GENERATOR_POINT with some random point;
// but it's too complex to prove.
// Also used by bitcoin-core and bitcoinjs-lib
export const TAPROOT_UNSPENDABLE_KEY: Bytes = sha256(Point.BASE.toRawBytes(false));
export type BTC_NETWORK = {
bech32: string;
pubKeyHash: number;
scriptHash: number;
wif: number;
};
export const NETWORK: BTC_NETWORK = {
bech32: 'bc',
pubKeyHash: 0x00,
scriptHash: 0x05,
wif: 0x80,
};
export const TEST_NETWORK: BTC_NETWORK = {
bech32: 'tb',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
};
// Exported for tests, internal method
export function compareBytes(a: Bytes, b: Bytes): number {
if (!isBytes(a) || !isBytes(b)) throw new Error(`cmp: wrong type a=${typeof a} b=${typeof b}`);
// -1 -> a<b, 0 -> a==b, 1 -> a>b
const len = Math.min(a.length, b.length);
for (let i = 0; i < len; i++) if (a[i] != b[i]) return Math.sign(a[i] - b[i]);
return Math.sign(a.length - b.length);
}