From 081aa9a22e7694e9a3398c9175d4d367854b2f56 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Tue, 11 Feb 2025 11:55:08 -0800 Subject: [PATCH 1/4] 328: Add test vectors --- bip-0328.mediawiki | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/bip-0328.mediawiki b/bip-0328.mediawiki index 3c07daba..cfb90b18 100644 --- a/bip-0328.mediawiki +++ b/bip-0328.mediawiki @@ -59,7 +59,24 @@ partial signatures. ==Test Vectors== -TBD +* Aggregate pubkey 0354240c76b8f2999143301a99c7f721ee57eee0bce401df3afeaa9ae218c70f23 +** Synthetic xpub xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwXEKGEouzXE6QLLRxjatMcLLzJ5LV5Nib1BN7vJg6yp45yHHRbm +** Keys: +*** 03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9 +*** 02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9 +* Aggregate pubkey 0290539eede565f5d054f32cc0c220126889ed1e5d193baf15aef344fe59d4610c +** Synthetic xpub xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwVk5TFJk8Tw5WAdV3DhrGfbFA216sE9BsQQiSFTdudkETnKdg8k +** Keys: +*** 02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9 +*** 03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 +*** 023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66 +* Aggregate pubkey 022479f134cdb266141dab1a023cbba30a870f8995b95a91fc8464e56a7d41f8ea +** Synthetic xpub xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwUvaZYpysLX4wN59tjwU5pBuDjNrPEJbfxjLwn7ruzbXTcUTHkZ +** Keys: +*** 02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 +*** 023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66 +*** 02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9 +*** 03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9 ==Backwards Compatibility== From 7ab43ce11f47ed6416581ed81a3e42738bf2ddce Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Tue, 11 Feb 2025 12:38:24 -0800 Subject: [PATCH 2/4] 328: Add reference implementation --- bip-0328.mediawiki | 2 +- bip-0328/_base58.py | 176 ++++++++++++++++++++++++++++++ bip-0328/_bip327.py | 1 + bip-0328/_common.py | 47 ++++++++ bip-0328/_xpub.py | 244 ++++++++++++++++++++++++++++++++++++++++++ bip-0328/reference.py | 31 ++++++ bip-0328/vectors.json | 29 +++++ 7 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 bip-0328/_base58.py create mode 120000 bip-0328/_bip327.py create mode 100644 bip-0328/_common.py create mode 100644 bip-0328/_xpub.py create mode 100644 bip-0328/reference.py create mode 100644 bip-0328/vectors.json diff --git a/bip-0328.mediawiki b/bip-0328.mediawiki index cfb90b18..50ef8d0d 100644 --- a/bip-0328.mediawiki +++ b/bip-0328.mediawiki @@ -89,7 +89,7 @@ derivation can be done, and the signers will be able to produce a signature for ==Reference Implementation== -TBD +A Python reference implementation is available in this BIP's [[bip-0328|Auxiliary Files]]. ==Acknowledgements== diff --git a/bip-0328/_base58.py b/bip-0328/_base58.py new file mode 100644 index 00000000..e7ee1be8 --- /dev/null +++ b/bip-0328/_base58.py @@ -0,0 +1,176 @@ +""" +Base 58 conversion utilities +**************************** +""" + +# +# base58.py +# Original source: git://github.com/joric/brutus.git +# which was forked from git://github.com/samrushing/caesure.git +# +# Distributed under the MIT/X11 software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# + +from binascii import hexlify, unhexlify +from typing import List + +from _common import hash256 + + +b58_digits: str = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + + +def encode(b: bytes) -> str: + """ + Encode bytes to a base58-encoded string + + :param b: Bytes to encode + :return: Base58 encoded string of ``b`` + """ + + # Convert big-endian bytes to integer + n: int = int('0x0' + hexlify(b).decode('utf8'), 16) + + # Divide that integer into base58 + temp: List[str] = [] + while n > 0: + n, r = divmod(n, 58) + temp.append(b58_digits[r]) + res: str = ''.join(temp[::-1]) + + # Encode leading zeros as base58 zeros + czero: int = 0 + pad: int = 0 + for c in b: + if c == czero: + pad += 1 + else: + break + return b58_digits[0] * pad + res + +def decode(s: str) -> bytes: + """ + Decode a base58-encoding string, returning bytes + + :param s: Base48 string to decode + :return: Bytes encoded by ``s`` + """ + if not s: + return b'' + + # Convert the string to an integer + n: int = 0 + for c in s: + n *= 58 + if c not in b58_digits: + raise Exception('Character %r is not a valid base58 character' % c) + digit = b58_digits.index(c) + n += digit + + # Convert the integer to bytes + h: str = '%x' % n + if len(h) % 2: + h = '0' + h + res = unhexlify(h.encode('utf8')) + + # Add padding back. + pad = 0 + for c in s[:-1]: + if c == b58_digits[0]: + pad += 1 + else: + break + return b'\x00' * pad + res + +def decode_check(s: str) -> bytes: + """ + Decode a Base58Check encoded string, returning bytes + + :param s: Base58 string to decode + :return: Bytes encoded by ``s`` + """ + data = decode(s) + payload = data[:-4] + checksum = data[-4:] + calc_checksum = hash256(payload) + if checksum != calc_checksum[:4]: + raise ValueError("Invalid checksum") + return payload + +def encode_check(b: bytes) -> str: + checksum = hash256(b)[0:4] + data = b + checksum + return encode(data) + +def get_xpub_fingerprint(s: str) -> bytes: + """ + Get the parent fingerprint from an extended public key + + :param s: The extended pubkey + :return: The parent fingerprint bytes + """ + data = decode(s) + fingerprint = data[5:9] + return fingerprint + +def get_xpub_fingerprint_hex(xpub: str) -> str: + """ + Get the parent fingerprint as a hex string from an extended public key + + :param s: The extended pubkey + :return: The parent fingerprint as a hex string + """ + data = decode(xpub) + fingerprint = data[5:9] + return hexlify(fingerprint).decode() + +def to_address(b: bytes, version: bytes) -> str: + """ + Base58 Check Encode the data with the version number. + Used to encode legacy style addresses. + + :param b: The data to encode + :param version: The version number to encode with + :return: The Base58 Check Encoded string + """ + data = version + b + checksum = hash256(data)[0:4] + data += checksum + return encode(data) + +def xpub_to_pub_hex(xpub: str) -> str: + """ + Get the public key as a string from the extended public key. + + :param xpub: The extended pubkey + :return: The pubkey hex string + """ + data = decode(xpub) + pubkey = data[-37:-4] + return hexlify(pubkey).decode() + + +def xpub_to_xonly_pub_hex(xpub: str) -> str: + """ + Get the public key as a string from the extended public key. + + :param xpub: The extended pubkey + :return: The pubkey hex string + """ + data = decode(xpub) + pubkey = data[-36:-4] + return hexlify(pubkey).decode() + + +def xpub_main_2_test(xpub: str) -> str: + """ + Convert an extended pubkey from mainnet version to testnet version. + + :param xpub: The extended pubkey + :return: The extended pubkey re-encoded using testnet version bytes + """ + data = decode(xpub) + test_data = b'\x04\x35\x87\xCF' + data[4:-4] + checksum = hash256(test_data)[0:4] + return encode(test_data + checksum) diff --git a/bip-0328/_bip327.py b/bip-0328/_bip327.py new file mode 120000 index 00000000..a1995636 --- /dev/null +++ b/bip-0328/_bip327.py @@ -0,0 +1 @@ +../bip-0327/reference.py \ No newline at end of file diff --git a/bip-0328/_common.py b/bip-0328/_common.py new file mode 100644 index 00000000..9b01bad1 --- /dev/null +++ b/bip-0328/_common.py @@ -0,0 +1,47 @@ +""" +Common Classes and Utilities +**************************** +""" + +import hashlib + +def sha256(s: bytes) -> bytes: + """ + Perform a single SHA256 hash. + + :param s: Bytes to hash + :return: The hash + """ + return hashlib.new('sha256', s).digest() + + +def ripemd160(s: bytes) -> bytes: + """ + Perform a single RIPEMD160 hash. + + :param s: Bytes to hash + :return: The hash + """ + return hashlib.new('ripemd160', s).digest() + + +def hash256(s: bytes) -> bytes: + """ + Perform a double SHA256 hash. + A SHA256 is performed on the input, and then a second + SHA256 is performed on the result of the first SHA256 + + :param s: Bytes to hash + :return: The hash + """ + return sha256(sha256(s)) + + +def hash160(s: bytes) -> bytes: + """ + perform a single SHA256 hash followed by a single RIPEMD160 hash on the result of the SHA256 hash. + + :param s: Bytes to hash + :return: The hash + """ + return ripemd160(sha256(s)) diff --git a/bip-0328/_xpub.py b/bip-0328/_xpub.py new file mode 100644 index 00000000..12031e38 --- /dev/null +++ b/bip-0328/_xpub.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The HWI developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +""" +Key Classes and Utilities +************************* + +Classes and utilities for working with extended public keys, key origins, and other key related things. +""" + +import _base58 as base58 +from _common import ( + hash256, + hash160, +) +import binascii +import hmac +import hashlib +import struct +from typing import ( + Dict, + Optional, + Sequence, + Tuple, +) + + +HARDENED_FLAG = 1 << 31 + +p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F +n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8) + +Point = Optional[Tuple[int, int]] + +def H_(x: int) -> int: + """ + Shortcut function that "hardens" a number in a BIP44 path. + """ + return x | HARDENED_FLAG + +def is_hardened(i: int) -> bool: + """ + Returns whether an index is hardened + """ + return i & HARDENED_FLAG != 0 + + +def point_add(p1: Point, p2: Point) -> Point: + if (p1 is None): + return p2 + if (p2 is None): + return p1 + if (p1[0] == p2[0] and p1[1] != p2[1]): + return None + if (p1 == p2): + lam = (3 * p1[0] * p1[0] * pow(2 * p1[1], p - 2, p)) % p + else: + lam = ((p2[1] - p1[1]) * pow(p2[0] - p1[0], p - 2, p)) % p + x3 = (lam * lam - p1[0] - p2[0]) % p + return (x3, (lam * (p1[0] - x3) - p1[1]) % p) + + +def point_mul(p: Point, n: int) -> Point: + r = None + for i in range(256): + if ((n >> i) & 1): + r = point_add(r, p) + p = point_add(p, p) + return r + + +def deserialize_point(b: bytes) -> Point: + x = int.from_bytes(b[1:], byteorder="big") + y = pow((x * x * x + 7) % p, (p + 1) // 4, p) + if (y & 1 != b[0] & 1): + y = p - y + return (x, y) + + +def bytes_to_point(point_bytes: bytes) -> Point: + header = point_bytes[0] + if header == 4: + x = point_bytes = point_bytes[1:33] + y = point_bytes = point_bytes[33:65] + return (int(binascii.hexlify(x), 16), int(binascii.hexlify(y), 16)) + return deserialize_point(point_bytes) + +def point_to_bytes(p: Point) -> bytes: + if p is None: + raise ValueError("Cannot convert None to bytes") + return (b'\x03' if p[1] & 1 else b'\x02') + p[0].to_bytes(32, byteorder="big") + + +# An extended public key (xpub) or private key (xprv). Just a data container for now. +# Only handles deserialization of extended keys into component data to be handled by something else +class ExtendedKey(object): + """ + A BIP 32 extended public key. + """ + + MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' + MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' + TESTNET_PUBLIC = b'\x04\x35\x87\xCF' + TESTNET_PRIVATE = b'\x04\x35\x83\x94' + + def __init__(self, version: bytes, depth: int, parent_fingerprint: bytes, child_num: int, chaincode: bytes, privkey: Optional[bytes], pubkey: bytes) -> None: + """ + :param version: The version bytes for this xpub + :param depth: The depth of this xpub as defined in BIP 32 + :param parent_fingerprint: The 4 byte fingerprint of the parent xpub as defined in BIP 32 + :param child_num: The number of this xpub as defined in BIP 32 + :param chaincode: The chaincode of this xpub as defined in BIP 32 + :param privkey: The private key for this xpub if available + :param pubkey: The public key for this xpub + """ + self.version: bytes = version + self.is_testnet: bool = version == ExtendedKey.TESTNET_PUBLIC or version == ExtendedKey.TESTNET_PRIVATE + self.is_private: bool = version == ExtendedKey.MAINNET_PRIVATE or version == ExtendedKey.TESTNET_PRIVATE + self.depth: int = depth + self.parent_fingerprint: bytes = parent_fingerprint + self.child_num: int = child_num + self.chaincode: bytes = chaincode + self.pubkey: bytes = pubkey + self.privkey: Optional[bytes] = privkey + + @classmethod + def deserialize(cls, xpub: str) -> 'ExtendedKey': + """ + Create an :class:`~ExtendedKey` from a Base58 check encoded xpub + + :param xpub: The Base58 check encoded xpub + """ + data = base58.decode(xpub)[:-4] # Decoded xpub without checksum + return cls.from_bytes(data) + + @classmethod + def from_bytes(cls, data: bytes) -> 'ExtendedKey': + """ + Create an :class:`~ExtendedKey` from a serialized xpub + + :param xpub: The serialized xpub + """ + + version = data[0:4] + if version not in [ExtendedKey.MAINNET_PRIVATE, ExtendedKey.MAINNET_PUBLIC, ExtendedKey.TESTNET_PRIVATE, ExtendedKey.TESTNET_PUBLIC]: + raise Exception(f"Extended key magic of {version.hex()} is invalid") + is_private = version == ExtendedKey.MAINNET_PRIVATE or version == ExtendedKey.TESTNET_PRIVATE + depth = data[4] + parent_fingerprint = data[5:9] + child_num = struct.unpack('>I', data[9:13])[0] + chaincode = data[13:45] + + if is_private: + privkey = data[46:] + pubkey = point_to_bytes(point_mul(G, int.from_bytes(privkey, byteorder="big"))) + return cls(version, depth, parent_fingerprint, child_num, chaincode, privkey, pubkey) + else: + pubkey = data[45:78] + return cls(version, depth, parent_fingerprint, child_num, chaincode, None, pubkey) + + def serialize(self) -> bytes: + """ + Serialize the ExtendedKey with the serialization format described in BIP 32. + Does not create an xpub string, but the bytes serialized here can be Base58 check encoded into one. + + :return: BIP 32 serialized extended key + """ + r = self.version + struct.pack('B', self.depth) + self.parent_fingerprint + struct.pack('>I', self.child_num) + self.chaincode + if self.is_private: + if self.privkey is None: + raise ValueError("Somehow we are private but don't have a privkey") + r += b"\x00" + self.privkey + else: + r += self.pubkey + return r + + def to_string(self) -> str: + """ + Serialize the ExtendedKey as a Base58 check encoded xpub string + + :return: Base58 check encoded xpub + """ + data = self.serialize() + checksum = hash256(data)[0:4] + return base58.encode(data + checksum) + + def get_printable_dict(self) -> Dict[str, object]: + """ + Get the attributes of this ExtendedKey as a dictionary that can be printed + + :return: Dictionary containing ExtendedKey information that can be printed + """ + d: Dict[str, object] = {} + d['testnet'] = self.is_testnet + d['private'] = self.is_private + d['depth'] = self.depth + d['parent_fingerprint'] = binascii.hexlify(self.parent_fingerprint).decode() + d['child_num'] = self.child_num + d['chaincode'] = binascii.hexlify(self.chaincode).decode() + if self.is_private and isinstance(self.privkey, bytes): + d['privkey'] = binascii.hexlify(self.privkey).decode() + d['pubkey'] = binascii.hexlify(self.pubkey).decode() + return d + + def derive_pub(self, i: int) -> 'ExtendedKey': + """ + Derive the public key at the given child index. + + :param i: The child index of the pubkey to derive + """ + if is_hardened(i): + raise ValueError("Index cannot be larger than 2^31") + + # Data to HMAC. Same as CKDpriv() for public child key. + data = self.pubkey + struct.pack(">L", i) + + # Get HMAC of data + Ihmac = hmac.new(self.chaincode, data, hashlib.sha512).digest() + Il = Ihmac[:32] + Ir = Ihmac[32:] + + # Construct curve point Il*G+K + Il_int = int(binascii.hexlify(Il), 16) + child_pubkey = point_add(point_mul(G, Il_int), bytes_to_point(self.pubkey)) + + # Construct and return a new BIP32Key + pubkey = point_to_bytes(child_pubkey) + chaincode = Ir + fingerprint = hash160(self.pubkey)[0:4] + return ExtendedKey(ExtendedKey.TESTNET_PUBLIC if self.is_testnet else ExtendedKey.MAINNET_PUBLIC, self.depth + 1, fingerprint, i, chaincode, None, pubkey) + + def derive_pub_path(self, path: Sequence[int]) -> 'ExtendedKey': + """ + Derive the public key at the given path + + :param path: Sequence of integers for the path of the pubkey to derive + """ + key = self + for i in path: + key = key.derive_pub(i) + return key diff --git a/bip-0328/reference.py b/bip-0328/reference.py new file mode 100644 index 00000000..d7e493e2 --- /dev/null +++ b/bip-0328/reference.py @@ -0,0 +1,31 @@ +#! /usr/bin/env python3 + +import json +import os +import sys + +from _base58 import xpub_to_pub_hex +from _bip327 import cbytes, key_agg +from _xpub import ExtendedKey + +CHAINCODE = bytes.fromhex("868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965") + +def aggregate_to_xpub(aggregate: bytes) -> ExtendedKey: + return ExtendedKey(ExtendedKey.MAINNET_PUBLIC, 0, b"\x00\x00\x00\x00", 0, CHAINCODE, None, aggregate) + +def test_aggregate_to_xpub(): + with open(os.path.join(sys.path[0], "vectors.json"), "r") as f: + test_data = json.load(f) + + for test_case in test_data: + keys = [bytes.fromhex(k) for k in test_case["keys"]] + + agg_ctx = key_agg(keys) + pub = cbytes(agg_ctx.Q) + assert pub.hex() == test_case["aggregate_pubkey"] + + xpub = aggregate_to_xpub(pub) + assert xpub.to_string() == test_case["xpub"] + +if __name__ == "__main__": + test_aggregate_to_xpub() diff --git a/bip-0328/vectors.json b/bip-0328/vectors.json new file mode 100644 index 00000000..d42c2674 --- /dev/null +++ b/bip-0328/vectors.json @@ -0,0 +1,29 @@ +[ + { + "aggregate_pubkey": "0354240c76b8f2999143301a99c7f721ee57eee0bce401df3afeaa9ae218c70f23", + "keys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9" + ], + "xpub": "xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwXEKGEouzXE6QLLRxjatMcLLzJ5LV5Nib1BN7vJg6yp45yHHRbm" + }, + { + "aggregate_pubkey": "0290539eede565f5d054f32cc0c220126889ed1e5d193baf15aef344fe59d4610c", + "keys": [ + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66" + ], + "xpub": "xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwVk5TFJk8Tw5WAdV3DhrGfbFA216sE9BsQQiSFTdudkETnKdg8k" + }, + { + "aggregate_pubkey": "022479f134cdb266141dab1a023cbba30a870f8995b95a91fc8464e56a7d41f8ea", + "keys": [ + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9" + ], + "xpub": "xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwUvaZYpysLX4wN59tjwU5pBuDjNrPEJbfxjLwn7ruzbXTcUTHkZ" + } +] From 3827648acc24e74f4e873f1cac6b1b59f2a50d8f Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Wed, 12 Feb 2025 09:59:59 -0800 Subject: [PATCH 3/4] 328: Correct Created date Date that the BIP number was assigned is 2024-06-04. --- bip-0328.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-0328.mediawiki b/bip-0328.mediawiki index 50ef8d0d..e1c2b6d9 100644 --- a/bip-0328.mediawiki +++ b/bip-0328.mediawiki @@ -7,7 +7,7 @@ Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0328 Status: Draft Type: Informational - Created: 2024-01-15 + Created: 2024-06-04 License: CC0-1.0 From 151ec96c83b4c59453c553f49cf3183fbb9176b4 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Wed, 12 Feb 2025 10:00:35 -0800 Subject: [PATCH 4/4] 328: Draft -> Proposed --- README.mediawiki | 4 ++-- bip-0328.mediawiki | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.mediawiki b/README.mediawiki index e89ccf5b..5df4f4d3 100644 --- a/README.mediawiki +++ b/README.mediawiki @@ -1029,13 +1029,13 @@ Those proposing changes should consider that ultimately consent may rest with th | Jonas Nick, Tim Ruffing, Elliott Jin | Informational | Active -|- +|- style="background-color: #ffffcf" | [[bip-0328.mediawiki|328]] | Applications | Derivation Scheme for MuSig2 Aggregate Keys | Ava Chow | Informational -| Draft +| Proposed |- | [[bip-0329.mediawiki|329]] | Applications diff --git a/bip-0328.mediawiki b/bip-0328.mediawiki index e1c2b6d9..26c5913b 100644 --- a/bip-0328.mediawiki +++ b/bip-0328.mediawiki @@ -5,7 +5,7 @@ Author: Ava Chow Comments-Summary: No comments yet. Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0328 - Status: Draft + Status: Proposed Type: Informational Created: 2024-06-04 License: CC0-1.0