Esempio n. 1
0
    def test_key_deployment(self):
        """GEC 2: Test Vectors for SEC 1, section 4.1

        http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf
        """

        # 4.1.1
        # ec = secp160r1
        # hf = sha1

        # 4.1.2
        dU = 971761939728640320549601132085879836204587084162
        self.assertEqual(format(dU,
                                str(ec.psize) + 'x'),
                         'aa374ffc3ce144e6b073307972cb6d57b2a4e982')
        QU = mult(dU, ec.G, ec)
        self.assertEqual(QU,
                         (466448783855397898016055842232266600516272889280,
                          1110706324081757720403272427311003102474457754220))
        self.assertEqual(
            octets_from_point(QU, True, ec).hex(),
            '0251b4496fecc406ed0e75a24a3c03206251419dc0')

        # 4.1.3
        dV = 399525573676508631577122671218044116107572676710
        self.assertEqual(format(dV,
                                str(ec.psize) + 'x'),
                         '45fb58a92a17ad4b15101c66e74f277e2b460866')
        QV = mult(dV, ec.G, ec)
        self.assertEqual(QV,
                         (420773078745784176406965940076771545932416607676,
                          221937774842090227911893783570676792435918278531))
        self.assertEqual(
            octets_from_point(QV, True, ec).hex(),
            '0349b41e0e9c0369c2328739d90f63d56707c6e5bc')

        # expected results
        z_exp = 1155982782519895915997745984453282631351432623114
        zstr = 'ca7c0f8c3ffa87a96e1b74ac8e6af594347bb40a'
        size = 20
        keying_data_exp = '744ab703f5bc082e59185f6d049d2d367db245c2'

        # 4.1.4
        z, _ = mult(dU, QV, ec)  # x coordinate only
        self.assertEqual(z, z_exp)
        self.assertEqual(format(z, str(ec.psize) + 'x'), zstr)
        keyingdata = dh.ansi_x963_kdf(octets_from_int(z, ec.psize), size, ec,
                                      hf)
        self.assertEqual(keyingdata.hex(), keying_data_exp)

        # 4.1.5
        z, _ = mult(dV, QU, ec)  # x coordinate only
        self.assertEqual(z, z_exp)
        self.assertEqual(format(z, str(ec.psize) + 'x'), zstr)
        keyingdata = dh.ansi_x963_kdf(octets_from_int(z, ec.psize), size, ec,
                                      hf)
        self.assertEqual(keyingdata.hex(), keying_data_exp)
def sign(msg: bytes,
                   k: List[int],
                   sign_key_idx: List[int],
                   sign_keys: List[int],
                   pubk_rings: Dict[int, List[Point]]) -> Tuple[bytes, Dict[int, List[int]]]:
    """ Borromean ring signature - signing algorithm

        https://github.com/ElementsProject/borromean-signatures-writeup
        https://github.com/Blockstream/borromean_paper/blob/master/borromean_draft_0.01_9ade1e49.pdf

        inputs:
        - msg: msg to be signed (bytes)
        - sign_key_idx: list of indexes representing each signing key per ring
        - sign_keys: list containing the whole set of signing keys (one per ring)
        - pubk_rings: dictionary of lists where internal lists represent single rings of pubkeys
    """

    s: Dict[int, List[int]] = defaultdict(list)
    e: Dict[int, List[int]] = defaultdict(list)
    m = _get_msg_format(msg, pubk_rings)
    e0bytes = m
    ring_size = len(pubk_rings)
    # step 1
    for i in range(ring_size):
        keys_size = len(pubk_rings[i])
        s[i] = [0]*keys_size
        e[i] = [0]*keys_size
        j_star = sign_key_idx[i]
        start_idx = (j_star + 1) % keys_size
        R = octets_from_point(ec, mult(ec, k[i], ec.G), True)
        if start_idx != 0:
            for j in range(start_idx, keys_size):
                s[i][j] = random.getrandbits(256)
                e[i][j] = int_from_bits(ec, _hash(m, R, i, j))
                assert 0 < e[i][j] < ec.n, "sign fail: how did you do that?!?"
                T = double_mult(ec, s[i][j], ec.G, -e[i][j], pubk_rings[i][j])
                R = octets_from_point(ec, T, True)
        e0bytes += R
    e0 = hf(e0bytes).digest()
    # step 2
    for i in range(ring_size):
        e[i][0] = int_from_bits(ec, _hash(m, e0, i, 0))
        assert 0 < e[i][0] < ec.n, "sign fail: how did you do that?!?"
        j_star = sign_key_idx[i]
        for j in range(1, j_star+1):
            s[i][j-1] = random.getrandbits(256)
            T = double_mult(ec, s[i][j-1], ec.G, -e[i][j-1], pubk_rings[i][j-1])
            R = octets_from_point(ec, T, True)
            e[i][j] = int_from_bits(ec, _hash(m, R, i, j))
            assert 0 < e[i][j] < ec.n, "sign fail: how did you do that?!?"
        s[i][j_star] = k[i] + sign_keys[i]*e[i][j_star]
    return e0, s
def xpub_from_xprv(xprv: octets) -> bytes:
    """Neutered Derivation (ND)

    Computation of the extended public key corresponding to an extended
    private key (“neutered” as it removes the ability to sign transactions)
    """

    xprv = base58.decode_check(xprv, 78)
    if xprv[45] != 0:
        raise ValueError("extended key is not a private one")

    i = PRV.index(xprv[:4])

    # serialization data
    xpub = PUB[i]  # version
    # unchanged serialization data
    xpub += xprv[4:5]  # depth
    xpub += xprv[5:9]  # parent pubkey fingerprint
    xpub += xprv[9:13]  # child index
    xpub += xprv[13:45]  # chain code

    p = int_from_octets(xprv[46:])
    P = mult(ec, p, ec.G)
    xpub += octets_from_point(ec, P, True)  # public key
    return base58.encode_check(xpub)
    def test_gec(self):
        """ GEC 2: Test Vectors for SEC 1, section 2

            http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf
        """
        # 2.1.1 Scheme setup
        ec = secp160r1
        hf = sha1

        # 2.1.2 Key Deployment for U
        dU = 971761939728640320549601132085879836204587084162
        self.assertEqual(format(dU,
                                str(ec.psize) + 'x'),
                         'aa374ffc3ce144e6b073307972cb6d57b2a4e982')
        QU = mult(ec, dU, ec.G)
        self.assertEqual(QU,
                         (466448783855397898016055842232266600516272889280,
                          1110706324081757720403272427311003102474457754220))
        self.assertEqual(
            octets_from_point(ec, QU, True).hex(),
            '0251b4496fecc406ed0e75a24a3c03206251419dc0')

        # 2.1.3 Signing Operation for U
        msg = 'abc'.encode()
        k = 702232148019446860144825009548118511996283736794
        exp_sig = (0xCE2873E5BE449563391FEB47DDCBA2DC16379191,
                   0x3480EC1371A091A464B31CE47DF0CB8AA2D98B54)
        sig = dsa.sign(ec, hf, msg, dU, k)
        r, s = sig
        self.assertEqual(r, exp_sig[0])
        self.assertIn(s, (exp_sig[1], ec.n - exp_sig[1]))

        # 2.1.4 Verifying Operation for V
        self.assertTrue(dsa.verify(ec, hf, msg, QU, sig))
        self.assertTrue(dsa._verify(ec, hf, msg, QU, sig))
Esempio n. 5
0
    def test_p2wpkh_address(self):

        # https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
        # leading/trailing spaces should be tolerated
        pub = " 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"
        addr = b'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'
        self.assertEqual(addr, p2wpkh_address(pub))
        addr = b'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx'
        self.assertEqual(addr, p2wpkh_address(pub, 'testnet'))

        # http://bitcoinscri.pt/pages/segwit_native_p2wpkh_address
        pub = "02530c548d402670b13ad8887ff99c294e67fc18097d236d57880c69261b42def7"
        addr = b'bc1qg9stkxrszkdqsuj92lm4c7akvk36zvhqw7p6ck'
        self.assertEqual(addr, p2wpkh_address(pub))

        _, _, wp = _decode(addr)
        self.assertEqual(bytes(wp), hash160(pub))

        # Uncompressed pubkey
        uncompr_pub = octets_from_point(point_from_octets(pub, ec), False, ec)
        self.assertRaises(ValueError, p2wpkh_address, uncompr_pub)
        # p2wpkh_address(uncompr_pub)

        # Wrong pubkey size: 34 instead of 33
        self.assertRaises(ValueError, p2wpkh_address, pub + '00')
        # p2wpkh_address(pub + '00')

        # Witness program length (21) is not 20
        self.assertRaises(ValueError, _p2wpkh_address,
                          hash160(pub) + b'\x00', True, "mainnet")
Esempio n. 6
0
    def test_p2pkh_address_from_pubkey(self):
        # https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
        pub = '0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352'
        addr = p2pkh_address(pub)
        self.assertEqual(addr, b'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs')
        _, _, hash2 = h160_from_base58_address(addr)
        self.assertEqual(hash2, hash160(pub))

        uncompressed_pub = octets_from_point(point_from_octets(pub, ec), False,
                                             ec)
        addr = p2pkh_address(uncompressed_pub)
        self.assertEqual(addr, b'16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM')
        _, _, hash2 = h160_from_base58_address(addr)
        self.assertEqual(hash2, hash160(uncompressed_pub))

        # trailing/leading spaces in string
        pub = '  0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352'
        addr = p2pkh_address(pub)
        self.assertEqual(addr, b'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs')
        _, _, hash2 = h160_from_base58_address(addr)
        self.assertEqual(hash2, hash160(pub))

        pub = '0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352  '
        addr = p2pkh_address(pub)
        self.assertEqual(addr, b'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs')
def _verify(msg: bytes,
                      e0: bytes,
                      s: Dict[int, List[int]],
                      pubk_rings: Dict[int, List[Point]]) -> bool:

    ring_size = len(pubk_rings)
    m = _get_msg_format(msg, pubk_rings)
    e: Dict[int, List[int]] = defaultdict(list)
    e0bytes = m
    for i in range(ring_size):
        keys_size = len(pubk_rings[i])
        e[i] = [0]*keys_size
        e[i][0] = int_from_bits(ec, _hash(m, e0, i, 0))
        assert e[i][0] != 0, "invalid sig: how did you do that?!?"
        R = b'\0x00'
        for j in range(keys_size):
            T = double_mult(ec, s[i][j], ec.G, -e[i][j], pubk_rings[i][j])
            R = octets_from_point(ec, T, True)
            if j != len(pubk_rings[i])-1:
                e[i][j+1] = int_from_bits(ec, _hash(m, R, i, j+1))
                assert e[i][j+1] != 0, "invalid sig: how did you do that?!?"
            else:
                e0bytes += R
    e0_prime = hf(e0bytes).digest()
    return e0_prime == e0
def _e(ec: Curve, hf, r: int, P: Point, mhd: bytes) -> int:
    # Let e = int(hf(bytes(x(R)) || bytes(dG) || mhd)) mod n.
    ebytes = octets_from_int(r, ec.psize)  # FIXME: hsize, nsize ?
    ebytes += octets_from_point(ec, P, True)
    ebytes += mhd
    ebytes = hf(ebytes).digest()
    e = int_from_bits(ec, ebytes)
    return e
Esempio n. 9
0
    def test_all_curves(self):
        for ec in all_curves:
            self.assertEqual(mult(ec, 0, ec.G), Inf)
            self.assertEqual(mult(ec, 0, ec.G), Inf)

            self.assertEqual(mult(ec, 1, ec.G), ec.G)
            self.assertEqual(mult(ec, 1, ec.G), ec.G)

            Gy_odd = ec.y_odd(ec.G[0], True)
            self.assertEqual(Gy_odd % 2, 1)
            Gy_even = ec.y_odd(ec.G[0], False)
            self.assertEqual(Gy_even % 2, 0)
            self.assertTrue(ec.G[1] in (Gy_odd, Gy_even))

            Gbytes = octets_from_point(ec, ec.G, True)
            G2 = point_from_octets(ec, Gbytes)
            self.assertEqual(ec.G, G2)

            Gbytes = octets_from_point(ec, ec.G, False)
            G2 = point_from_octets(ec, Gbytes)
            self.assertEqual(ec.G, G2)

            P = ec.add(Inf, ec.G)
            self.assertEqual(P, ec.G)
            P = ec.add(ec.G, Inf)
            self.assertEqual(P, ec.G)
            P = ec.add(Inf, Inf)
            self.assertEqual(P, Inf)

            P = ec.add(ec.G, ec.G)
            self.assertEqual(P, mult(ec, 2, ec.G))

            P = mult(ec, ec.n-1, ec.G)
            self.assertEqual(ec.add(P, ec.G), Inf)
            self.assertEqual(mult(ec, ec.n, ec.G), Inf)

            self.assertEqual(mult(ec, 0, Inf), Inf)
            self.assertEqual(mult(ec, 1, Inf), Inf)
            self.assertEqual(mult(ec, 25, Inf), Inf)

            ec_repr = repr(ec)
            if ec in low_card_curves or ec.psize < 24:
                ec_repr = ec_repr[:-1] + ", False)"
            ec2 = eval(ec_repr)
            self.assertEqual(str(ec), str(ec2))
def address_from_pubkey(Q: Point, compressed: bool, version: bytes = b'\x00') -> bytes:
    """Public key to (bytes) address"""

    # also check that the Point is on curve
    pubkey = octets_from_point(ec, Q, compressed)

    # FIXME: this is mainnet only
    vh160 = version + _h160(pubkey)
    return base58.encode_check(vh160)
Esempio n. 11
0
def _e(ec: Curve, hf: Callable[[Any], Any], r: int, P: Point,
       mhd: bytes) -> int:
    # Let e = int(hf(bytes(x(R)) || bytes(dG) || mhd)) mod n.
    h = hf()
    h.update(octets_from_int(r, ec.psize))
    h.update(octets_from_point(ec, P, True))
    h.update(mhd)
    e = int_from_bits(ec, h.digest())
    return e
def _get_msg_format(msg: bytes, pubk_rings: Dict[int, List[Point]]) -> bytes:
    m = msg
    rings = len(pubk_rings)
    for i in range(rings):
        keys = len(pubk_rings[i])
        for j in range(keys):
            P = pubk_rings[i][j]
            Pbytes = octets_from_point(ec, P, True)
            m += Pbytes
    return hf(m).digest()
Esempio n. 13
0
def _tweak(c: bytes, ec: Curve, hf, k: int) -> Tuple[Point, int]:
    """tweak kG

    returns:
    - point kG to tweak
    - tweaked private key k + h(kG||c), the corresponding pubkey is a commitment to kG, c
    """
    R = mult(ec, k, ec.G)
    e = hf(octets_from_point(ec, R, True) + c).digest()
    e = int.from_bytes(e, 'big')
    return R, (e + k) % ec.n
Esempio n. 14
0
def verify_commit(c: bytes, ec: Curve, hf, receipt: Receipt) -> bool:
    w, R = receipt
    # w in [1..n-1] dsa
    # w in [1..p-1] ssa
    # different verify functions?

    # verify R is a good point?

    ch = hf(c).digest()
    e = hf(octets_from_point(ec, R, True) + ch).digest()
    e = int_from_bits(ec, e)
    W = ec.add(R, mult(ec, e, ec.G))
    # different verify functions?
    # return w == W[0] # ECSS
    return w == W[0] % ec.n  # ECDS, FIXME: ECSSA
Esempio n. 15
0
def second_generator(ec: Curve, hf) -> Point:
    """Nothing-Up-My-Sleeve (NUMS) second generator H wrt ec.G 

       source: https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/rangeproof/main_impl.h
       idea: https://crypto.stackexchange.com/questions/25581/second-generator-for-secp256k1-curve
       Get the hash of G, then coerce it to a point (hx, hy).
       The resulting point could not be a curvepoint: in this case keep on
       incrementing hx until a valid curve point (hx, hy) is obtained.
    """
    G_bytes = octets_from_point(ec, ec.G, False)
    hd = hf(G_bytes).digest()
    hx = int_from_bits(ec, hd)
    isCurvePoint = False
    while not isCurvePoint:
        try:
            hy = ec.y_odd(hx, False)
            isCurvePoint = True
        except:
            hx += 1
    return Point(hx, hy)
Esempio n. 16
0
    def test_musig(self):
        """ testing 3-of-3 MuSig
        
            https://eprint.iacr.org/2018/068
            https://blockstream.com/2018/01/23/musig-key-aggregation-schnorr-signatures.html
            https://medium.com/@snigirev.stepan/how-schnorr-signatures-may-improve-bitcoin-91655bcb4744
        """
        ec = secp256k1
        L: List[Point] = list()  # multiset of public keys
        M = hf('message to sign'.encode()).digest()

        # first signer
        q1 = int_from_octets(
            '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d92ad1d')
        Q1 = mult(ec, q1, ec.G)
        L.append(octets_from_point(ec, Q1, False))

        # ephemeral private nonce
        k1 = 0x012a2a833eac4e67e06611aba01345b85cdd4f5ad44f72e369ef0dd640424dbb
        K1 = mult(ec, k1, ec.G)
        K1_x = K1[0]
        if legendre_symbol(K1[1], ec._p) != 1:
            k1 = ec.n - k1
            K1 = K1_x, ec.y_quadratic_residue(K1_x, True)
            #K1 = mult(ec, k1, ec.G)

        # second signer
        q2 = int_from_octets(
            '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d')
        Q2 = mult(ec, q2, ec.G)
        L.append(octets_from_point(ec, Q2, False))

        k2 = 0x01a2a0d3eac4e67e06611aba01345b85cdd4f5ad44f72e369ef0dd640424dbdb
        K2 = mult(ec, k2, ec.G)
        K2_x = K2[0]
        if legendre_symbol(K2[1], ec._p) != 1:
            k2 = ec.n - k2
            K2 = K2_x, ec.y_quadratic_residue(K2_x, True)
            #K2 = mult(ec, k2, ec.G)

        # third signer
        q3 = random.getrandbits(ec.nlen) % ec.n
        Q3 = mult(ec, q3, ec.G)
        while Q3 == None:  # plausible only for small (test) cardinality groups
            q3 = random.getrandbits(ec.nlen) % ec.n
            Q3 = mult(ec, q3, ec.G)
        L.append(octets_from_point(ec, Q3, False))

        k3 = random.getrandbits(ec.nlen) % ec.n
        K3 = mult(ec, k3, ec.G)
        while K3 == None:  # plausible only for small (test) cardinality groups
            k3 = random.getrandbits(ec.nlen) % ec.n
            K3 = mult(ec, k3, ec.G)
        K3_x = K3[0]
        if legendre_symbol(K3[1], ec._p) != 1:
            k3 = ec.n - k3
            K3 = K3_x, ec.y_quadratic_residue(K3_x, True)
            #K3 = mult(ec, k3, ec.G)

        L.sort()  # using lexicographic ordering
        L_brackets = b''
        for i in range(len(L)):
            L_brackets += L[i]

        h1 = hf(L_brackets + octets_from_point(ec, Q1, False)).digest()
        a1 = int_from_bits(ec, h1)
        h2 = hf(L_brackets + octets_from_point(ec, Q2, False)).digest()
        a2 = int_from_bits(ec, h2)
        h3 = hf(L_brackets + octets_from_point(ec, Q3, False)).digest()
        a3 = int_from_bits(ec, h3)
        # aggregated public key
        Q_All = double_mult(ec, a1, Q1, a2, Q2)
        Q_All = ec.add(Q_All, mult(ec, a3, Q3))
        Q_All_bytes = octets_from_point(ec, Q_All, True)

        ########################
        # exchange K_x, compute s
        # WARNING: the signers should exchange commitments to the public
        #          nonces before sending the nonces themselves

        # first signer use K2_x and K3_x
        y = ec.y_quadratic_residue(K2_x, True)
        K2_recovered = (K2_x, y)
        y = ec.y_quadratic_residue(K3_x, True)
        K3_recovered = (K3_x, y)
        K1_All = ec.add(ec.add(K1, K2_recovered), K3_recovered)
        if legendre_symbol(K1_All[1], ec._p) != 1:
            # no need to actually change K1_All[1], as it is not used anymore
            # let's fix k1 instead, as it is used later
            k1 = ec.n - k1
        K1_All0_bytes = K1_All[0].to_bytes(32, byteorder="big")
        h1 = hf(K1_All0_bytes + Q_All_bytes + M).digest()
        c1 = int_from_bits(ec, h1)
        assert 0 < c1 and c1 < ec.n, "sign fail"
        s1 = (k1 + c1 * a1 * q1) % ec.n

        # second signer use K1_x and K3_x
        y = ec.y_quadratic_residue(K1_x, True)
        K1_recovered = (K1_x, y)
        y = ec.y_quadratic_residue(K3_x, True)
        K3_recovered = (K3_x, y)
        K2_All = ec.add(ec.add(K2, K1_recovered), K3_recovered)
        if legendre_symbol(K2_All[1], ec._p) != 1:
            # no need to actually change K2_All[1], as it is not used anymore
            # let's fix k2 instead, as it is used later
            k2 = ec.n - k2
        K2_All0_bytes = K2_All[0].to_bytes(32, byteorder="big")
        h2 = hf(K2_All0_bytes + Q_All_bytes + M).digest()
        c2 = int_from_bits(ec, h2)
        assert 0 < c2 and c2 < ec.n, "sign fail"
        s2 = (k2 + c2 * a2 * q2) % ec.n

        # third signer use K1_x and K2_x
        y = ec.y_quadratic_residue(K1_x, True)
        K1_recovered = (K1_x, y)
        y = ec.y_quadratic_residue(K2_x, True)
        K2_recovered = (K2_x, y)
        K3_All = ec.add(ec.add(K1_recovered, K2_recovered), K3)
        if legendre_symbol(K3_All[1], ec._p) != 1:
            # no need to actually change K3_All[1], as it is not used anymore
            # let's fix k3 instead, as it is used later
            k3 = ec.n - k3
        K3_All0_bytes = K3_All[0].to_bytes(32, byteorder="big")
        h3 = hf(K3_All0_bytes + Q_All_bytes + M).digest()
        c3 = int_from_bits(ec, h3)
        assert 0 < c3 and c3 < ec.n, "sign fail"
        s3 = (k3 + c3 * a3 * q3) % ec.n

        ############################################
        # combine signatures into a single signature

        # anyone can do the following
        assert K1_All[0] == K2_All[0], "sign fail"
        assert K2_All[0] == K3_All[0], "sign fail"
        s_All = (s1 + s2 + s3) % ec.n
        sig = (K1_All[0], s_All)

        self.assertTrue(ssa.verify(ec, hf, M, Q_All, sig))
Esempio n. 17
0
# ==master ext private key==
# depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ...
depth = b'\x00'
# This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key)
child_number = b'\x00\x00\x00\x00'
# the fingerprint of the parent's public key (0x00000000 if master key)
fingerprint = b'\x00\x00\x00\x00'
idf = depth + fingerprint + child_number

# master private key, master public key, chain code
hd = HMAC(b"Bitcoin seed", seed.to_bytes(seed_bytes, 'big'), sha512).digest()
qbytes = hd[:32]
p = int(qbytes.hex(), 16) % ec.n
qbytes = b'\x00' + p.to_bytes(32, 'big')
Q = mult(p, ec.G)
Qbytes = octets_from_point(Q, True)
chain_code = hd[32:]

#extended keys
ext_prv = encode(xprv + idf + chain_code + qbytes)
print("\nm")
print(ext_prv)
ext_pub = encode(xpub + idf + chain_code + Qbytes)
print("M")
print(ext_pub)
assert ext_prv == b"xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6", "failure"
assert ext_pub == b"xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", "failure"

# ==first (0) hardened child==
depth = b'\x01'
child_n = 0 + 0x80000000  #hardened
Esempio n. 18
0
    def test_octets2point(self):
        for ec in all_curves:
            Q = mult(ec, ec._p, ec.G)  # just a random point, not Inf

            Q_bytes = b'\x03' if Q[1] & 1 else b'\x02'
            Q_bytes += Q[0].to_bytes(ec.psize, "big")
            R = point_from_octets(ec, Q_bytes)
            self.assertEqual(R, Q)
            self.assertEqual(octets_from_point(ec, R, True), Q_bytes)

            Q_hex_str = Q_bytes.hex()
            R = point_from_octets(ec, Q_hex_str)
            self.assertEqual(R, Q)

            Q_bytes = b'\x04' + Q[0].to_bytes(ec.psize, "big")
            Q_bytes += Q[1].to_bytes(ec.psize, "big")
            R = point_from_octets(ec, Q_bytes)
            self.assertEqual(R, Q)
            self.assertEqual(octets_from_point(ec, R, False), Q_bytes)

            Q_hex_str = Q_bytes.hex()
            R = point_from_octets(ec, Q_hex_str)
            self.assertEqual(R, Q)

            # infinity point
            self.assertEqual(point_from_octets(ec, b'\x00'), Inf)
            self.assertEqual(octets_from_point(ec, Inf, True),  b'\x00')
            self.assertEqual(octets_from_point(ec, Inf, False), b'\x00')
            Inf_hex_str = b'\x00'.hex()
            self.assertEqual(point_from_octets(ec, Inf_hex_str), Inf)

            # scalar in point multiplication can be int, str, or bytes
            t = tuple()
            self.assertRaises(TypeError, mult, ec, t, ec.G)

            # not a compressed point
            Q_bytes = b'\x01' * (ec.psize+1)
            self.assertRaises(ValueError, point_from_octets, ec, Q_bytes)
            # not a point
            Q_bytes += b'\x01'
            self.assertRaises(ValueError, point_from_octets, ec, Q_bytes)
            # not an uncompressed point
            Q_bytes = b'\x01' * 2 * (ec.psize+1)
            self.assertRaises(ValueError, point_from_octets, ec, Q_bytes)
        
        # invalid x coordinate
        ec = secp256k1
        x = 0xEEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34
        xstr = format(x, '32X')
        self.assertRaises(ValueError, point_from_octets, ec, "03" + xstr)
        self.assertRaises(ValueError, point_from_octets, ec, "04" + xstr + xstr)
        self.assertRaises(ValueError, octets_from_point, ec, (x, x), True)
        self.assertRaises(ValueError, octets_from_point, ec, (x, x), False)

        # Point must be a tuple[int, int]
        P = x, x, x
        self.assertRaises(ValueError, ec.is_on_curve, P)

        # y-coordinate not in (0, p)
        P = x, ec._p+1
        self.assertRaises(ValueError, ec.is_on_curve, P)
def ckd(xparentkey: octets, index: Union[octets, int]) -> bytes:
    """Child Key Derivation (CDK)

    Key derivation is normal if the extended parent key is public or
    child_index is less than 0x80000000.

    Key derivation is hardened if the extended parent key is private and
    child_index is not less than 0x80000000.
    """

    if isinstance(index, int):
        index = index.to_bytes(4, 'big')
    elif isinstance(index, str):  # hex string
        index = bytes.fromhex(index)

    if len(index) != 4:
        raise ValueError(f"a 4 bytes int is required, not {len(index)}")

    xparent = base58.decode_check(xparentkey, 78)

    version = xparent[:4]

    # serialization data
    xkey = version  # version
    xkey += (xparent[4] + 1).to_bytes(1, 'big')  # (increased) depth

    if (version in PUB):
        if xparent[45] not in (2, 3):  # not a compressed public key
            raise ValueError("version/key mismatch in extended parent key")
        Parent_bytes = xparent[45:]
        Parent = point_from_octets(ec, Parent_bytes)
        xkey += _h160(Parent_bytes)[:4]  # parent pubkey fingerprint
        if index[0] >= 0x80:
            raise ValueError("no private/hardened derivation from pubkey")
        xkey += index  # child index
        parent_chain_code = xparent[13:45]  # normal derivation
        # actual extended key (key + chain code) derivation
        h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest()
        offset = int.from_bytes(h[:32], 'big')
        Offset = mult(ec, offset, ec.G)
        Child = ec.add(Parent, Offset)
        Child_bytes = octets_from_point(ec, Child, True)
        xkey += h[32:]  # chain code
        xkey += Child_bytes  # public key
    elif (version in PRV):
        if xparent[45] != 0:  # not a private key
            raise ValueError("version/key mismatch in extended parent key")
        parent = int.from_bytes(xparent[46:], 'big')
        Parent = mult(ec, parent, ec.G)
        Parent_bytes = octets_from_point(ec, Parent, True)
        xkey += _h160(Parent_bytes)[:4]  # parent pubkey fingerprint
        xkey += index  # child index
        # actual extended key (key + chain code) derivation
        parent_chain_code = xparent[13:45]
        if (index[0] < 0x80):  # normal derivation
            h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest()
        else:  # hardened derivation
            h = HMAC(parent_chain_code, xparent[45:] + index, sha512).digest()
        offset = int.from_bytes(h[:32], 'big')
        child = (parent + offset) % ec.n
        child_bytes = b'\x00' + child.to_bytes(32, 'big')
        xkey += h[32:]  # chain code
        xkey += child_bytes  # private key
    else:
        raise ValueError("invalid extended key version")

    return base58.encode_check(xkey)
Esempio n. 20
0
# depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ...
depth = b'\x00'
# This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key)
child_number = b'\x00\x00\x00\x00'
# the fingerprint of the parent's public key (0x00000000 if master key)
fingerprint = b'\x00\x00\x00\x00'
idf = depth + fingerprint + child_number

# master private key, master public key, chain code
hd = HMAC(b"Bitcoin seed", seed.to_bytes(seed_bytes, byteorder='big'),
          sha512).digest()
qbytes = hd[:32]
q = int(qbytes.hex(), 16) % ec.n
qbytes = b'\x00' + q.to_bytes(32, byteorder='big')
Q = mult(ec, q, ec.G)
Qbytes = octets_from_point(ec, Q, True)
chain_code = hd[32:]

#extended keys
ext_prv = encode_check(xprv + idf + chain_code + qbytes)
print("\nm")
print(ext_prv)
ext_pub = encode_check(xpub + idf + chain_code + Qbytes)
print("M")
print(ext_pub)
assert ext_prv == b"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", "failure"
assert ext_pub == b"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", "failure"

# ==first (0) hardened child==
depth = b'\x01'
child_n = 0 + 0x80000000  #hardened
Esempio n. 21
0
    def test_threshold(self):
        """testing 2-of-3 threshold signature (Pedersen secret sharing)"""

        ec = secp256k1
        hf = sha256
        # parameters
        t = 2
        H = second_generator(ec, hf)
        msg = hf(b'message to sign').digest()

        ### FIRST PHASE: key pair generation ###

        # signer one acting as the dealer
        commits1: List[Point] = list()
        q1 = (1 + random.getrandbits(ec.nlen)) % ec.n
        q1_prime = (1 + random.getrandbits(ec.nlen)) % ec.n
        commits1.append(double_mult(q1_prime, H, q1))

        # sharing polynomials
        f1: List[int] = list()
        f1.append(q1)
        f1_prime: List[int] = list()
        f1_prime.append(q1_prime)
        for i in range(1, t):
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f1.append(temp)
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f1_prime.append(temp)
            commits1.append(double_mult(f1_prime[i], H, f1[i]))

        # shares of the secret
        alpha12 = 0  # share of q1 belonging to P2
        alpha12_prime = 0
        alpha13 = 0  # share of q1 belonging to P3
        alpha13_prime = 0
        for i in range(t):
            alpha12 += (f1[i] * pow(2, i)) % ec.n
            alpha12_prime += (f1_prime[i] * pow(2, i)) % ec.n

            alpha13 += (f1[i] * pow(3, i)) % ec.n
            alpha13_prime += (f1_prime[i] * pow(3, i)) % ec.n

        # player two verifies consistency of his share
        RHS = 1, 0
        for i in range(t):
            RHS = ec.add(RHS, mult(pow(2, i), commits1[i]))
        assert double_mult(alpha12_prime, H,
                           alpha12) == RHS, 'player one is cheating'

        # player three verifies consistency of his share
        RHS = 1, 0
        for i in range(t):
            RHS = ec.add(RHS, mult(pow(3, i), commits1[i]))
        assert double_mult(alpha13_prime, H,
                           alpha13) == RHS, 'player one is cheating'

        # signer two acting as the dealer
        commits2: List[Point] = list()
        q2 = (1 + random.getrandbits(ec.nlen)) % ec.n
        q2_prime = (1 + random.getrandbits(ec.nlen)) % ec.n
        commits2.append(double_mult(q2_prime, H, q2))

        # sharing polynomials
        f2: List[int] = list()
        f2.append(q2)
        f2_prime: List[int] = list()
        f2_prime.append(q2_prime)
        for i in range(1, t):
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f2.append(temp)
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f2_prime.append(temp)
            commits2.append(double_mult(f2_prime[i], H, f2[i]))

        # shares of the secret
        alpha21 = 0  # share of q2 belonging to P1
        alpha21_prime = 0
        alpha23 = 0  # share of q2 belonging to P3
        alpha23_prime = 0
        for i in range(t):
            alpha21 += (f2[i] * pow(1, i)) % ec.n
            alpha21_prime += (f2_prime[i] * pow(1, i)) % ec.n

            alpha23 += (f2[i] * pow(3, i)) % ec.n
            alpha23_prime += (f2_prime[i] * pow(3, i)) % ec.n

        # player one verifies consistency of his share
        RHS = 1, 0
        for i in range(t):
            RHS = ec.add(RHS, mult(pow(1, i), commits2[i]))
        assert double_mult(alpha21_prime, H,
                           alpha21) == RHS, 'player two is cheating'

        # player three verifies consistency of his share
        RHS = 1, 0
        for i in range(t):
            RHS = ec.add(RHS, mult(pow(3, i), commits2[i]))
        assert double_mult(alpha23_prime, H,
                           alpha23) == RHS, 'player two is cheating'

        # signer three acting as the dealer
        commits3: List[Point] = list()
        q3 = (1 + random.getrandbits(ec.nlen)) % ec.n
        q3_prime = (1 + random.getrandbits(ec.nlen)) % ec.n
        commits3.append(double_mult(q3_prime, H, q3))

        # sharing polynomials
        f3: List[int] = list()
        f3.append(q3)
        f3_prime: List[int] = list()
        f3_prime.append(q3_prime)
        for i in range(1, t):
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f3.append(temp)
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f3_prime.append(temp)
            commits3.append(double_mult(f3_prime[i], H, f3[i]))

        # shares of the secret
        alpha31 = 0  # share of q3 belonging to P1
        alpha31_prime = 0
        alpha32 = 0  # share of q3 belonging to P2
        alpha32_prime = 0
        for i in range(t):
            alpha31 += (f3[i] * pow(1, i)) % ec.n
            alpha31_prime += (f3_prime[i] * pow(1, i)) % ec.n

            alpha32 += (f3[i] * pow(2, i)) % ec.n
            alpha32_prime += (f3_prime[i] * pow(2, i)) % ec.n

        # player one verifies consistency of his share
        RHS = 1, 0
        for i in range(t):
            RHS = ec.add(RHS, mult(pow(1, i), commits3[i]))
        assert double_mult(alpha31_prime, H,
                           alpha31) == RHS, 'player three is cheating'

        # player two verifies consistency of his share
        RHS = 1, 0
        for i in range(t):
            RHS = ec.add(RHS, mult(pow(2, i), commits3[i]))
        assert double_mult(alpha32_prime, H,
                           alpha32) == RHS, 'player two is cheating'

        # shares of the secret key q = q1 + q2 + q3
        alpha1 = (alpha21 + alpha31) % ec.n
        alpha2 = (alpha12 + alpha32) % ec.n
        alpha3 = (alpha13 + alpha23) % ec.n
        for i in range(t):
            alpha1 += (f1[i] * pow(1, i)) % ec.n
            alpha2 += (f2[i] * pow(2, i)) % ec.n
            alpha3 += (f3[i] * pow(3, i)) % ec.n

        # it's time to recover the public key Q = Q1 + Q2 + Q3 = (q1 + q2 + q3)G
        A1: List[Point] = list()
        A2: List[Point] = list()
        A3: List[Point] = list()

        # each participant i = 1, 2, 3 shares Qi as follows

        # he broadcasts these values
        for i in range(t):
            A1.append(mult(f1[i]))
            A2.append(mult(f2[i]))
            A3.append(mult(f3[i]))

        # he checks the others' values
        # player one
        RHS2 = 1, 0
        RHS3 = 1, 0
        for i in range(t):
            RHS2 = ec.add(RHS2, mult(pow(1, i), A2[i]))
            RHS3 = ec.add(RHS3, mult(pow(1, i), A3[i]))
        assert mult(alpha21) == RHS2, 'player two is cheating'
        assert mult(alpha31) == RHS3, 'player three is cheating'

        # player two
        RHS1 = 1, 0
        RHS3 = 1, 0
        for i in range(t):
            RHS1 = ec.add(RHS1, mult(pow(2, i), A1[i]))
            RHS3 = ec.add(RHS3, mult(pow(2, i), A3[i]))
        assert mult(alpha12) == RHS1, 'player one is cheating'
        assert mult(alpha32) == RHS3, 'player three is cheating'

        # player three
        RHS1 = 1, 0
        RHS2 = 1, 0
        for i in range(t):
            RHS1 = ec.add(RHS1, mult(pow(3, i), A1[i]))
            RHS2 = ec.add(RHS2, mult(pow(3, i), A2[i]))
        assert mult(alpha13) == RHS1, 'player one is cheating'
        assert mult(alpha23) == RHS2, 'player two is cheating'

        A: List[Point] = list()  # commitment at the global sharing polynomial
        for i in range(t):
            A.append(ec.add(A1[i], ec.add(A2[i], A3[i])))

        Q = A[0]  # aggregated public key

        ### SECOND PHASE: generation of the nonces' pair ###
        # This phase follows exactly the key generation procedure
        # suppose that player one and three want to sign

        # signer one acting as the dealer
        commits1: List[Point] = list()
        k1 = (1 + random.getrandbits(ec.nlen)) % ec.n
        k1_prime = (1 + random.getrandbits(ec.nlen)) % ec.n
        commits1.append(double_mult(k1_prime, H, k1))

        # sharing polynomials
        f1: List[int] = list()
        f1.append(k1)
        f1_prime: List[int] = list()
        f1_prime.append(k1_prime)
        for i in range(1, t):
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f1.append(temp)
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f1_prime.append(temp)
            commits1.append(double_mult(f1_prime[i], H, f1[i]))

        # shares of the secret
        beta13 = 0  # share of k1 belonging to P3
        beta13_prime = 0
        for i in range(t):
            beta13 += (f1[i] * pow(3, i)) % ec.n
            beta13_prime += (f1_prime[i] * pow(3, i)) % ec.n

        # player three verifies consistency of his share
        RHS = 1, 0
        for i in range(t):
            RHS = ec.add(RHS, mult(pow(3, i), commits1[i]))
        assert double_mult(beta13_prime, H,
                           beta13) == RHS, 'player one is cheating'

        # signer three acting as the dealer
        commits3: List[Point] = list()
        k3 = (1 + random.getrandbits(ec.nlen)) % ec.n
        k3_prime = (1 + random.getrandbits(ec.nlen)) % ec.n
        commits3.append(double_mult(k3_prime, H, k3))

        # sharing polynomials
        f3: List[int] = list()
        f3.append(k3)
        f3_prime: List[int] = list()
        f3_prime.append(k3_prime)
        for i in range(1, t):
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f3.append(temp)
            temp = (1 + random.getrandbits(ec.nlen)) % ec.n
            f3_prime.append(temp)
            commits3.append(double_mult(f3_prime[i], H, f3[i]))

        # shares of the secret
        beta31 = 0  # share of k3 belonging to P1
        beta31_prime = 0
        for i in range(t):
            beta31 += (f3[i] * pow(1, i)) % ec.n
            beta31_prime += (f3_prime[i] * pow(1, i)) % ec.n

        # player one verifies consistency of his share
        RHS = 1, 0
        for i in range(t):
            RHS = ec.add(RHS, mult(pow(1, i), commits3[i]))
        assert double_mult(beta31_prime, H,
                           beta31) == RHS, 'player three is cheating'

        # shares of the secret nonce
        beta1 = beta31 % ec.n
        beta3 = beta13 % ec.n
        for i in range(t):
            beta1 += (f1[i] * pow(1, i)) % ec.n
            beta3 += (f3[i] * pow(3, i)) % ec.n

        # it's time to recover the public nonce
        B1: List[Point] = list()
        B3: List[Point] = list()

        # each participant i = 1, 3 shares Qi as follows

        # he broadcasts these values
        for i in range(t):
            B1.append(mult(f1[i]))
            B3.append(mult(f3[i]))

        # he checks the others' values
        # player one
        RHS3 = 1, 0
        for i in range(t):
            RHS3 = ec.add(RHS3, mult(pow(1, i), B3[i]))
        assert mult(beta31) == RHS3, 'player three is cheating'

        # player three
        RHS1 = 1, 0
        for i in range(t):
            RHS1 = ec.add(RHS1, mult(pow(3, i), B1[i]))
        assert mult(beta13) == RHS1, 'player one is cheating'

        B: List[Point] = list()  # commitment at the global sharing polynomial
        for i in range(t):
            B.append(ec.add(B1[i], B3[i]))

        K = B[0]  # aggregated public nonce
        if legendre_symbol(K[1], ec._p) != 1:
            beta1 = ec.n - beta1
            beta3 = ec.n - beta3

        ### PHASE THREE: signature generation ###

        # partial signatures
        ebytes = K[0].to_bytes(32, byteorder='big')
        ebytes += octets_from_point(Q, True, ec)
        ebytes += msg
        e = int_from_bits(hf(ebytes).digest(), ec)
        gamma1 = (beta1 + e * alpha1) % ec.n
        gamma3 = (beta3 + e * alpha3) % ec.n

        # each participant verifies the other partial signatures

        # player one
        if legendre_symbol(K[1], ec._p) == 1:
            RHS3 = ec.add(K, mult(e, Q))
            for i in range(1, t):
                temp = double_mult(pow(3, i), B[i], e * pow(3, i), A[i])
                RHS3 = ec.add(RHS3, temp)
        else:
            assert legendre_symbol(K[1], ec._p) != 1
            RHS3 = ec.add(ec.opposite(K), mult(e, Q))
            for i in range(1, t):
                temp = double_mult(pow(3, i), ec.opposite(B[i]), e * pow(3, i),
                                   A[i])
                RHS3 = ec.add(RHS3, temp)

        assert mult(gamma3) == RHS3, 'player three is cheating'

        # player three
        if legendre_symbol(K[1], ec._p) == 1:
            RHS1 = ec.add(K, mult(e, Q))
            for i in range(1, t):
                temp = double_mult(pow(1, i), B[i], e * pow(1, i), A[i])
                RHS1 = ec.add(RHS1, temp)
        else:
            assert legendre_symbol(K[1], ec._p) != 1
            RHS1 = ec.add(ec.opposite(K), mult(e, Q))
            for i in range(1, t):
                temp = double_mult(pow(1, i), ec.opposite(B[i]), e * pow(1, i),
                                   A[i])
                RHS1 = ec.add(RHS1, temp)

        assert mult(gamma1) == RHS1, 'player two is cheating'

        ### PHASE FOUR: aggregating the signature ###
        omega1 = 3 * mod_inv(3 - 1, ec.n) % ec.n
        omega3 = 1 * mod_inv(1 - 3, ec.n) % ec.n
        sigma = (gamma1 * omega1 + gamma3 * omega3) % ec.n

        sig = K[0], sigma

        self.assertTrue(ssa._verify(msg, Q, sig))

        ### ADDITIONAL PHASE: reconstruction of the private key ###
        secret = (omega1 * alpha1 + omega3 * alpha3) % ec.n
        self.assertEqual((q1 + q2 + q3) % ec.n, secret)
Esempio n. 22
0
    def test_musig(self):
        """testing 3-of-3 MuSig

            https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/musig/musig.md
            https://blockstream.com/2019/02/18/musig-a-new-multisignature-standard/
            https://eprint.iacr.org/2018/068
            https://blockstream.com/2018/01/23/musig-key-aggregation-schnorr-signatures.html
            https://medium.com/@snigirev.stepan/how-schnorr-signatures-may-improve-bitcoin-91655bcb4744
        """
        M = sha256(b'message to sign').digest()

        ec = secp256k1
        hf = sha256

        # key setup is not interactive

        # first signer
        q1 = int_from_octets(
            '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d92ad1d')
        Q1 = mult(q1)
        k1 = rfc6979(M, q1)
        K1 = mult(k1)

        # second signer
        q2 = int_from_octets(
            '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d')
        Q2 = mult(q2)
        k2 = rfc6979(M, q2)
        K2 = mult(k2)

        # third signer
        q3 = int_from_octets(
            '0c28fca386c7aff7600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d')
        Q3 = mult(q3)
        k3 = rfc6979(M, q3)
        K3 = mult(k3)

        # this is MuSig core: the rest is just Schnorr signature additivity
        L: List[Point] = list()  # multiset of public keys
        L.append(octets_from_point(Q1, False, ec))
        L.append(octets_from_point(Q2, False, ec))
        L.append(octets_from_point(Q3, False, ec))
        L.sort()  # using lexicographic ordering
        L_brackets = b''
        for i in range(len(L)):
            L_brackets += L[i]
        h1 = hf(L_brackets + octets_from_point(Q1, False, ec)).digest()
        a1 = int_from_bits(h1, ec)
        h2 = hf(L_brackets + octets_from_point(Q2, False, ec)).digest()
        a2 = int_from_bits(h2, ec)
        h3 = hf(L_brackets + octets_from_point(Q3, False, ec)).digest()
        a3 = int_from_bits(h3, ec)
        # aggregated public key
        Q = ec.add(double_mult(a1, Q1, a2, Q2), mult(a3, Q3))
        Q_bytes = octets_from_point(Q, True, ec)

        ########################
        # interactive signature: exchange K, compute s
        # WARNING: the signers should exchange commitments to the public
        #          nonces before sending the nonces themselves

        # first signer
        # K, r_bytes, and e as calculated by any signer
        # are the same as the ones by the other signers
        K = ec.add(ec.add(K1, K2), K3)
        r_bytes = K[0].to_bytes(32, byteorder='big')
        e = int_from_bits(hf(r_bytes + Q_bytes + M).digest(), ec)
        if legendre_symbol(K[1], ec._p) != 1:
            # no need to actually change K[1], as it is not used anymore
            # let's fix k1 instead, as it is used later
            # note that all other signers will change their k too
            k1 = ec.n - k1
        s1 = (k1 + e * a1 * q1) % ec.n

        # second signer
        # K, r_bytes, and e as calculated by any signer
        # are the same as the ones by the other signers
        if legendre_symbol(K[1], ec._p) != 1:
            # no need to actually change K[1], as it is not used anymore
            # let's fix k2 instead, as it is used later
            # note that all other signers will change their k too
            k2 = ec.n - k2
        s2 = (k2 + e * a2 * q2) % ec.n

        # third signer
        # K, r_bytes, and e as calculated by any signer
        # are the same as the ones by the other signers
        if legendre_symbol(K[1], ec._p) != 1:
            # no need to actually change K[1], as it is not used anymore
            # let's fix k3 instead, as it is used later
            # note that all other signers will change their k too
            k3 = ec.n - k3
        s3 = (k3 + e * a3 * q3) % ec.n

        ############################################
        # interactive signature: exchange signatures
        # combine all (K[0], s) signatures into a single signature
        # anyone can do the following
        sig = K[0], (s1 + s2 + s3) % ec.n

        self.assertTrue(ssa.verify(M, Q, sig))