Skip to content

Commit 629d8f9

Browse files
authored
Merge pull request #128 from buidl-bitcoin/musig2
MuSig2 implementation
2 parents bb610d1 + 1b8c322 commit 629d8f9

14 files changed

+1389
-601
lines changed

buidl/block.py

+29-21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from io import BytesIO
2+
13
from buidl.helper import (
24
bits_to_target,
35
hash256,
@@ -9,19 +11,6 @@
911
from buidl.tx import Tx
1012

1113

12-
GENESIS_BLOCK_HASH = {
13-
"mainnet": bytes.fromhex(
14-
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
15-
),
16-
"testnet": bytes.fromhex(
17-
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
18-
),
19-
"signet": bytes.fromhex(
20-
"00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"
21-
),
22-
}
23-
24-
2514
class Block:
2615
command = b"block"
2716

@@ -58,21 +47,25 @@ def __repr__(self):
5847
"""
5948

6049
@classmethod
61-
def parse_header(cls, s):
50+
def parse_header(cls, stream=None, hex=None):
6251
"""Takes a byte stream and parses block headers. Returns a Block object"""
63-
# s.read(n) will read n bytes from the stream
52+
if hex:
53+
if stream:
54+
raise RuntimeError("One of stream or hex should be defined")
55+
stream = BytesIO(bytes.fromhex(hex))
56+
# stream.read(n) will read n bytes from the stream
6457
# version - 4 bytes, little endian, interpret as int
65-
version = little_endian_to_int(s.read(4))
58+
version = little_endian_to_int(stream.read(4))
6659
# prev_block - 32 bytes, little endian (use [::-1] to reverse)
67-
prev_block = s.read(32)[::-1]
60+
prev_block = stream.read(32)[::-1]
6861
# merkle_root - 32 bytes, little endian (use [::-1] to reverse)
69-
merkle_root = s.read(32)[::-1]
62+
merkle_root = stream.read(32)[::-1]
7063
# timestamp - 4 bytes, little endian, interpret as int
71-
timestamp = little_endian_to_int(s.read(4))
64+
timestamp = little_endian_to_int(stream.read(4))
7265
# bits - 4 bytes
73-
bits = s.read(4)
66+
bits = stream.read(4)
7467
# nonce - 4 bytes
75-
nonce = s.read(4)
68+
nonce = stream.read(4)
7669
# initialize class
7770
return cls(version, prev_block, merkle_root, timestamp, bits, nonce)
7871

@@ -177,3 +170,18 @@ def get_outpoints(self):
177170
for tx_out in t.tx_outs:
178171
if not tx_out.script_pubkey.has_op_return():
179172
yield (tx_out.script_pubkey.raw_serialize())
173+
174+
175+
GENESIS_BLOCK_MAINNET_HEX = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c"
176+
GENESIS_BLOCK_TESTNET_HEX = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae18"
177+
GENESIS_BLOCK_SIGNET_HEX = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a008f4d5fae77031e8ad22203"
178+
GENESIS_BLOCK_HEADERS = {
179+
"mainnet": Block.parse_header(hex=GENESIS_BLOCK_MAINNET_HEX),
180+
"testnet": Block.parse_header(hex=GENESIS_BLOCK_TESTNET_HEX),
181+
"signet": Block.parse_header(hex=GENESIS_BLOCK_SIGNET_HEX),
182+
}
183+
GENESIS_BLOCK_HASH = {
184+
"mainnet": GENESIS_BLOCK_HEADERS["mainnet"].hash(),
185+
"testnet": GENESIS_BLOCK_HEADERS["testnet"].hash(),
186+
"signet": GENESIS_BLOCK_HEADERS["signet"].hash(),
187+
}

buidl/cecc.py

+13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import hashlib
22
import hmac
3+
import secrets
34

45
from buidl.helper import (
56
big_endian_to_int,
@@ -348,6 +349,12 @@ def hex(self):
348349
return "{:x}".format(self.secret).zfill(64)
349350

350351
def sign(self, z):
352+
# per libsecp256k1 documentation, this helps against side-channel attacks
353+
if not lib.secp256k1_context_randomize(
354+
GLOBAL_CTX,
355+
secrets.token_bytes(32),
356+
):
357+
raise RuntimeError("libsecp256k1 context randomization error")
351358
secret = int_to_big_endian(self.secret, 32)
352359
msg = int_to_big_endian(z, 32)
353360
csig = ffi.new("secp256k1_ecdsa_signature *")
@@ -365,6 +372,12 @@ def sign_schnorr(self, msg, aux):
365372
raise ValueError("msg needs to be 32 bytes")
366373
if len(aux) != 32:
367374
raise ValueError("aux needs to be 32 bytes")
375+
# per libsecp256k1 documentation, this helps against side-channel attacks
376+
if not lib.secp256k1_context_randomize(
377+
GLOBAL_CTX,
378+
secrets.token_bytes(32),
379+
):
380+
raise RuntimeError("libsecp256k1 context randomization error")
368381
keypair = ffi.new("secp256k1_keypair *")
369382
if not lib.secp256k1_keypair_create(
370383
GLOBAL_CTX, keypair, int_to_big_endian(self.secret, 32)

buidl/chash.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from buidl._libsec import ffi, lib
2+
3+
4+
GLOBAL_CTX = ffi.gc(
5+
lib.secp256k1_context_create(
6+
lib.SECP256K1_CONTEXT_SIGN | lib.SECP256K1_CONTEXT_VERIFY
7+
),
8+
lib.secp256k1_context_destroy,
9+
)
10+
11+
12+
def tagged_hash(tag, msg):
13+
result = ffi.new("unsigned char [32]")
14+
tag_length = len(tag)
15+
msg_length = len(msg)
16+
if not lib.secp256k1_tagged_sha256(
17+
GLOBAL_CTX,
18+
result,
19+
tag,
20+
tag_length,
21+
msg,
22+
msg_length,
23+
):
24+
raise RuntimeError("libsecp256k1 tagged hash problem")
25+
return bytes(ffi.buffer(result, 32))
26+
27+
28+
def hash_aux(msg):
29+
return tagged_hash(b"BIP0340/aux", msg)
30+
31+
32+
def hash_challenge(msg):
33+
return tagged_hash(b"BIP0340/challenge", msg)
34+
35+
36+
def hash_keyaggcoef(msg):
37+
return tagged_hash(b"KeyAgg coefficient", msg)
38+
39+
40+
def hash_keyagglist(msg):
41+
return tagged_hash(b"KeyAgg list", msg)
42+
43+
44+
def hash_musignonce(msg):
45+
return tagged_hash(b"MuSig/noncecoef", msg)
46+
47+
48+
def hash_nonce(msg):
49+
return tagged_hash(b"BIP0340/nonce", msg)
50+
51+
52+
def hash_tapbranch(msg):
53+
return tagged_hash(b"TapBranch", msg)
54+
55+
56+
def hash_tapleaf(msg):
57+
return tagged_hash(b"TapLeaf", msg)
58+
59+
60+
def hash_tapsighash(msg):
61+
return tagged_hash(b"TapSighash", msg)
62+
63+
64+
def hash_taptweak(msg):
65+
return tagged_hash(b"TapTweak", msg)

buidl/hash.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
try:
2+
from buidl.chash import * # noqa: F401,F403
3+
except ModuleNotFoundError:
4+
from buidl.phash import * # noqa: F401,F403

buidl/helper.py

-43
Original file line numberDiff line numberDiff line change
@@ -449,48 +449,5 @@ def is_intable(int_as_string):
449449
return False
450450

451451

452-
TAGS = {
453-
"aux": sha256(b"BIP0340/aux") * 2,
454-
"nonce": sha256(b"BIP0340/nonce") * 2,
455-
"challenge": sha256(b"BIP0340/challenge") * 2,
456-
"tapbranch": sha256(b"TapBranch") * 2,
457-
"tapleaf": sha256(b"TapLeaf") * 2,
458-
"tapsighash": sha256(b"TapSighash") * 2,
459-
"taptweak": sha256(b"TapTweak") * 2,
460-
}
461-
462-
463-
def _tagged_hash(tag, msg):
464-
return sha256(TAGS[tag] + msg)
465-
466-
467-
def hash_aux(msg):
468-
return _tagged_hash("aux", msg)
469-
470-
471-
def hash_challenge(msg):
472-
return _tagged_hash("challenge", msg)
473-
474-
475-
def hash_nonce(msg):
476-
return _tagged_hash("nonce", msg)
477-
478-
479-
def hash_tapbranch(msg):
480-
return _tagged_hash("tapbranch", msg)
481-
482-
483-
def hash_tapleaf(msg):
484-
return _tagged_hash("tapleaf", msg)
485-
486-
487-
def hash_tapsighash(msg):
488-
return _tagged_hash("tapsighash", msg)
489-
490-
491-
def hash_taptweak(msg):
492-
return _tagged_hash("taptweak", msg)
493-
494-
495452
def xor_bytes(a, b):
496453
return bytes(x ^ y for x, y in zip(a, b))

buidl/libsec.h

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ typedef struct secp256k1_context_struct secp256k1_context;
88
secp256k1_context* secp256k1_context_create(
99
unsigned int flags
1010
);
11+
secp256k1_context* secp256k1_context_randomize(
12+
secp256k1_context* ctx,
13+
const unsigned char *seed32
14+
);
1115
void secp256k1_context_destroy(
1216
secp256k1_context* ctx
1317
);

buidl/pecc.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
import hmac
44
import hashlib
55

6+
from buidl.hash import (
7+
hash_aux,
8+
hash_challenge,
9+
hash_nonce,
10+
)
611
from buidl.helper import (
712
big_endian_to_int,
813
encode_base58_checksum,
914
hash160,
1015
hash256,
11-
hash_aux,
12-
hash_challenge,
13-
hash_nonce,
1416
int_to_big_endian,
1517
raw_decode_base58,
1618
xor_bytes,

buidl/phash.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import hashlib
2+
3+
4+
TAG_HASH_CACHE = {}
5+
6+
7+
def tagged_hash(tag: bytes, msg: bytes) -> bytes:
8+
if TAG_HASH_CACHE.get(tag) is None:
9+
TAG_HASH_CACHE[tag] = hashlib.sha256(tag).digest() * 2
10+
return hashlib.sha256(TAG_HASH_CACHE[tag] + msg).digest()
11+
12+
13+
def hash_aux(msg):
14+
return tagged_hash(b"BIP0340/aux", msg)
15+
16+
17+
def hash_challenge(msg):
18+
return tagged_hash(b"BIP0340/challenge", msg)
19+
20+
21+
def hash_keyaggcoef(msg):
22+
return tagged_hash(b"KeyAgg coefficient", msg)
23+
24+
25+
def hash_keyagglist(msg):
26+
return tagged_hash(b"KeyAgg list", msg)
27+
28+
29+
def hash_musignonce(msg):
30+
return tagged_hash(b"MuSig/noncecoef", msg)
31+
32+
33+
def hash_nonce(msg):
34+
return tagged_hash(b"BIP0340/nonce", msg)
35+
36+
37+
def hash_tapbranch(msg):
38+
return tagged_hash(b"TapBranch", msg)
39+
40+
41+
def hash_tapleaf(msg):
42+
return tagged_hash(b"TapLeaf", msg)
43+
44+
45+
def hash_tapsighash(msg):
46+
return tagged_hash(b"TapSighash", msg)
47+
48+
49+
def hash_taptweak(msg):
50+
return tagged_hash(b"TapTweak", msg)

buidl/script.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -549,11 +549,6 @@ def address(self, network="mainnet"):
549549
# convert to bech32 address using encode_bech32_checksum
550550
return encode_bech32_checksum(witness_program, network)
551551

552-
@classmethod
553-
def from_address(cls, address):
554-
_, _, point_raw = decode_bech32(address)
555-
return cls(point_raw)
556-
557552

558553
class WitnessScript(Script):
559554
"""Subclass that represents a WitnessScript for p2wsh"""
@@ -618,12 +613,17 @@ def address_to_script_pubkey(s):
618613
# p2sh
619614
h160 = decode_base58(s)
620615
return P2SHScriptPubKey(h160)
621-
elif s[:3] in ("bc1", "tb1"):
616+
elif s[:4] in ("bc1q", "tb1q"):
622617
if len(s) == 42:
623618
# p2wpkh
624619
return P2WPKHScriptPubKey(decode_bech32(s)[2])
625620
elif len(s) == 62:
626621
# p2wskh
627622
return P2WSHScriptPubKey(decode_bech32(s)[2])
623+
elif s[:4] in ("bc1p", "tb1p"):
624+
if len(s) != 62:
625+
raise RuntimeError(f"unknown type of address: {s}")
626+
# p2tr
627+
return P2TRScriptPubKey(decode_bech32(s)[2])
628628

629629
raise RuntimeError(f"unknown type of address: {s}")

0 commit comments

Comments
 (0)