Ejemplo n.º 1
0
def test_crack_prvkey() -> None:

    ec = CURVES["secp256k1"]

    q, x_Q = ssa.gen_keys()

    msg1 = "Paolo is afraid of ephemeral random numbers"
    m_1 = reduce_to_hlen(msg1)
    k = ssa._det_nonce(m_1, q)
    sig1 = ssa._sign(m_1, q, k)

    msg2 = "and Paolo is right to be afraid"
    m_2 = reduce_to_hlen(msg2)
    # reuse same k
    sig2 = ssa._sign(m_2, q, k)

    qc, kc = ssa.crack_prvkey(msg1, sig1, msg2, sig2, x_Q)
    assert q == qc
    assert k in (kc, ec.n - kc)

    with pytest.raises(BTClibValueError, match="not the same r in signatures"):
        ssa._crack_prvkey(m_1, sig1, m_2, (16, sig1[1]), x_Q)

    with pytest.raises(BTClibValueError, match="identical signatures"):
        ssa._crack_prvkey(m_1, sig1, m_1, sig1, x_Q)
Ejemplo n.º 2
0
def test_crack_prvkey() -> None:

    ec = CURVES["secp256k1"]

    q = 0x19E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725
    x_Q = mult(q)[0]

    msg1_str = "Paolo is afraid of ephemeral random numbers"
    msg1 = hf(msg1_str.encode()).digest()
    k, _ = ssa._det_nonce(msg1, q)
    sig1 = ssa._sign(msg1, q, k)

    msg2_str = "and Paolo is right to be afraid"
    msg2 = hf(msg2_str.encode()).digest()
    # reuse same k
    sig2 = ssa._sign(msg2, q, k)

    qc, kc = ssa._crack_prvkey(msg1, sig1, msg2, sig2, x_Q)
    assert q in (qc, ec.n - qc)
    assert k in (kc, ec.n - kc)

    with pytest.raises(ValueError, match="not the same r in signatures"):
        ssa._crack_prvkey(msg1, sig1, msg2, (16, sig1[1]), x_Q)

    with pytest.raises(ValueError, match="identical signatures"):
        ssa._crack_prvkey(msg1, sig1, msg1, sig1, x_Q)
Ejemplo n.º 3
0
def test_batch_validation() -> None:

    ec = CURVES["secp256k1"]

    hsize = hf().digest_size
    hlen = hsize * 8

    ms = []
    Qs = []
    sigs = []
    ms.append(secrets.randbits(hlen).to_bytes(hsize, "big"))
    q = 1 + secrets.randbelow(ec.n - 1)
    # bytes version
    Qs.append(mult(q, ec.G, ec)[0])
    sigs.append(ssa._sign(ms[0], q, None, ec, hf))
    # test with only 1 sig
    ssa._batch_verify(ms, Qs, sigs, ec, hf)
    for _ in range(3):
        m = secrets.randbits(hlen).to_bytes(hsize, "big")
        ms.append(m)
        q = 1 + secrets.randbelow(ec.n - 1)
        # Point version
        Qs.append(mult(q, ec.G, ec)[0])
        sigs.append(ssa._sign(m, q, None, ec, hf))
    ssa._batch_verify(ms, Qs, sigs, ec, hf)
    assert ssa.batch_verify(ms, Qs, sigs, ec, hf)

    ms.append(ms[0])
    sigs.append(sigs[1])
    Qs.append(Qs[0])
    assert not ssa.batch_verify(ms, Qs, sigs, ec, hf)
    err_msg = "signature verification precondition failed"
    with pytest.raises(ValueError, match=err_msg):
        ssa._batch_verify(ms, Qs, sigs, ec, hf)
    sigs[-1] = sigs[0]  # valid again

    ms[-1] = ms[0][:-1]
    err_msg = "invalid size: 31 bytes instead of 32"
    with pytest.raises(ValueError, match=err_msg):
        ssa._batch_verify(ms, Qs, sigs, ec, hf)
    ms[-1] = ms[0]  # valid again

    ms.append(ms[0])  # add extra message
    err_msg = "mismatch between number of pubkeys "
    with pytest.raises(ValueError, match=err_msg):
        ssa._batch_verify(ms, Qs, sigs, ec, hf)
    ms.pop()  # valid again

    sigs.append(sigs[0])  # add extra sig
    err_msg = "mismatch between number of pubkeys "
    with pytest.raises(ValueError, match=err_msg):
        ssa._batch_verify(ms, Qs, sigs, ec, hf)
    sigs.pop()  # valid again

    err_msg = "field prime is not equal to 3 mod 4: "
    with pytest.raises(ValueError, match=err_msg):
        ssa._batch_verify(ms, Qs, sigs, CURVES["secp224k1"], hf)
Ejemplo n.º 4
0
def test_low_cardinality() -> None:
    "test low-cardinality curves for all msg/key pairs."

    # ec.n has to be prime to sign
    test_curves = [
        low_card_curves["ec13_11"],
        low_card_curves["ec13_19"],
        low_card_curves["ec17_13"],
        low_card_curves["ec17_23"],
        low_card_curves["ec19_13"],
        low_card_curves["ec19_23"],
        low_card_curves["ec23_19"],
        low_card_curves["ec23_31"],
    ]

    # only low cardinality test curves or it would take forever
    for ec in test_curves:
        # BIP340 Schnorr only applies to curve whose prime p = 3 %4
        if not ec.pIsThreeModFour:
            err_msg = "field prime is not equal to 3 mod 4: "
            with pytest.raises(ValueError, match=err_msg):
                ssa._sign(32 * b"\x00", 1, None, ec)
            continue
        for q in range(1, ec.n // 2):  # all possible private keys
            QJ = _mult(q, ec.GJ, ec)  # public key
            x_Q = ec._x_aff_from_jac(QJ)
            if not ec.has_square_y(QJ):
                q = ec.n - q
                QJ = ec.negate_jac(QJ)
            for k in range(1, ec.n // 2):  # all possible ephemeral keys
                RJ = _mult(k, ec.GJ, ec)
                r = ec._x_aff_from_jac(RJ)
                if not ec.has_square_y(RJ):
                    k = ec.n - k
                for e in range(ec.n):  # all possible challenges
                    s = (k + e * q) % ec.n

                    sig = ssa.__sign(e, q, k, r, ec)
                    assert (r, s) == sig
                    # valid signature must validate
                    ssa.__assert_as_valid(e, QJ, r, s, ec)

                    # if e == 0 then the sig is valid for all {q, Q}
                    # no public key can be recovered
                    if e == 0:
                        err_msg = "invalid zero challenge"
                        with pytest.raises(ValueError, match=err_msg):
                            ssa.__recover_pubkey(e, r, s, ec)
                    else:
                        assert x_Q == ssa.__recover_pubkey(e, r, s, ec)
Ejemplo n.º 5
0
def test_bip340_vectors() -> None:
    """BIP340 (Schnorr) test vectors.

    https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
    """
    fname = "bip340_test_vectors.csv"
    filename = path.join(path.dirname(__file__), "test_data", fname)
    with open(filename, newline="") as csvfile:
        reader = csv.reader(csvfile)
        # skip column headers while checking that there are 7 columns
        _, _, _, _, _, _, _ = reader.__next__()
        for row in reader:
            (index, seckey, pubkey, m, sig, result, comment) = row
            err_msg = f"Test vector #{int(index)}"
            if seckey != "":
                _, pubkey_actual = ssa.gen_keys(seckey)
                assert pubkey == hex(pubkey_actual).upper()[2:], err_msg

                sig_actual = ssa.serialize(*ssa._sign(m, seckey))
                assert sig == sig_actual.hex().upper(), err_msg

            if comment:
                err_msg += ": " + comment
            # TODO what's worng with xor-ing ?
            # assert (result == "TRUE") ^ ssa._verify(m, pubkey, sig), err_msg
            if result == "TRUE":
                assert ssa._verify(m, pubkey, sig), err_msg
            else:
                assert not ssa._verify(m, pubkey, sig), err_msg
Ejemplo n.º 6
0
def test_invalid_schnorr():
    sighash = bytes.fromhex("00" * 32)
    sig = ssa.serialize(*ssa._sign(sighash, 1))
    pubkey = bytes_from_point(mult(1))
    pubkey_hash = hash256(pubkey)
    script = Script([
        [0x00, 0x00, pubkey],
        [0x01, 0x00, sig],
        [0x02, 0x00, pubkey_hash],  # push pubkey_hash
        [0x03, 0x02, b"\x00"],  # hash of pub key from unlocking script
        [0xFF, 0x01, b"\x03\x02"],  # check equality
        [0xFF, 0x04, b"\xff"],  # exit if not equal
        [0xFF, 0x03, b"\x00\x01"],  # schnorr verify
        [0xFF, 0x04, b"\xff"],
    ]  # exit if not equal])  # push signature
                    )
    assert script.execute(memory={0x100: sighash})
Ejemplo n.º 7
0
def test_bip340_vectors() -> None:
    """BIP340 (Schnorr) test vectors.

    https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
    """
    fname = "bip340_test_vectors.csv"
    filename = path.join(path.dirname(__file__), "test_data", fname)
    with open(filename, newline="") as csvfile:
        reader = csv.reader(csvfile)
        # skip column headers while checking that there are 7 columns
        _, _, _, _, _, _, _, _ = reader.__next__()
        for row in reader:
            (index, seckey, pubkey, aux_rand, m, sig, result, comment) = row
            err_msg = f"Test vector #{int(index)}"
            try:
                if seckey != "":
                    _, pubkey_actual = ssa.gen_keys(seckey)
                    assert pubkey == hex(pubkey_actual).upper()[2:], err_msg

                    k = ssa._det_nonce(m, seckey, aux_rand)
                    sig_actual = ssa._sign(m, seckey, k)
                    ssa._assert_as_valid(m, pubkey, sig_actual)
                    assert ssa.deserialize(sig) == sig_actual, err_msg

                if comment:
                    err_msg += ": " + comment
                # TODO what's wrong with xor-ing ?
                # assert (result == "TRUE") ^ ssa._verify(m, pubkey, sig), err_msg
                if result == "TRUE":
                    ssa._assert_as_valid(m, pubkey, sig)
                    assert ssa._verify(m, pubkey, sig), err_msg
                else:
                    assert not ssa._verify(m, pubkey, sig), err_msg
            except Exception as e:  # pragma: no cover # pylint: disable=broad-except
                print(err_msg)  # pragma: no cover
                raise e  # pragma: no cover
Ejemplo n.º 8
0
def test_signature() -> None:
    ec = CURVES["secp256k1"]
    msg = "Satoshi Nakamoto"

    q, x_Q = ssa.gen_keys(0x01)
    sig = ssa.sign(msg, q)
    ssa.assert_as_valid(msg, x_Q, sig)
    assert ssa.verify(msg, x_Q, sig)

    assert sig == ssa.deserialize(sig)

    ssa.assert_as_valid(msg, x_Q, sig)
    ssa.assert_as_valid(msg, x_Q, ssa.serialize(*sig))
    ssa.assert_as_valid(msg, x_Q, ssa.serialize(*sig).hex())

    msg_fake = "Craig Wright"
    assert not ssa.verify(msg_fake, x_Q, sig)
    err_msg = r"y_K is odd|signature verification failed"
    with pytest.raises(BTClibRuntimeError, match=err_msg):
        ssa.assert_as_valid(msg_fake, x_Q, sig)

    _, x_Q_fake = ssa.gen_keys(0x02)
    assert not ssa.verify(msg, x_Q_fake, sig)
    with pytest.raises(BTClibRuntimeError, match=err_msg):
        ssa.assert_as_valid(msg, x_Q_fake, sig)

    _, x_Q_fake = ssa.gen_keys(0x4)
    assert not ssa.verify(msg, x_Q_fake, sig)
    with pytest.raises(BTClibRuntimeError, match=err_msg):
        ssa.assert_as_valid(msg, x_Q_fake, sig)

    err_msg = "not a BIP340 public key"
    with pytest.raises(BTClibValueError, match=err_msg):
        ssa.assert_as_valid(msg, INF, sig)  # type: ignore
    with pytest.raises(BTClibValueError, match=err_msg):
        ssa.point_from_bip340pubkey(INF)  # type: ignore

    sig_fake = (sig[0], sig[1], sig[1])
    assert not ssa.verify(msg, x_Q, sig_fake)  # type: ignore
    err_msg = "too many values to unpack "
    with pytest.raises(ValueError, match=err_msg):
        ssa.assert_as_valid(msg, x_Q, sig_fake)  # type: ignore

    sig_invalid = ec.p, sig[1]
    assert not ssa.verify(msg, x_Q, sig_invalid)
    err_msg = "x-coordinate not in 0..p-1: "
    with pytest.raises(BTClibValueError, match=err_msg):
        ssa.assert_as_valid(msg, x_Q, sig_invalid)

    sig_invalid = sig[0], ec.p
    assert not ssa.verify(msg, x_Q, sig_invalid)
    err_msg = "scalar s not in 0..n-1: "
    with pytest.raises(BTClibValueError, match=err_msg):
        ssa.assert_as_valid(msg, x_Q, sig_invalid)

    m_fake = b"\x00" * 31
    err_msg = "invalid size: 31 bytes instead of 32"
    with pytest.raises(BTClibValueError, match=err_msg):
        ssa._assert_as_valid(m_fake, x_Q, sig)

    with pytest.raises(BTClibValueError, match=err_msg):
        ssa._sign(m_fake, q)

    err_msg = "private key not in 1..n-1: "
    with pytest.raises(BTClibValueError, match=err_msg):
        ssa.sign(msg, 0)

    # ephemeral key not in 1..n-1
    err_msg = "private key not in 1..n-1: "
    with pytest.raises(BTClibValueError, match=err_msg):
        ssa._sign(reduce_to_hlen(msg, hf), q, 0)
    with pytest.raises(BTClibValueError, match=err_msg):
        ssa._sign(reduce_to_hlen(msg, hf), q, ec.n)

    err_msg = "invalid zero challenge"
    with pytest.raises(BTClibValueError, match=err_msg):
        ssa.__recover_pubkey(0, sig[0], sig[1], ec)
Ejemplo n.º 9
0
def unlock_p2pkh(sighash, prvkey):
    sig = ssa.serialize(*ssa._sign(sighash, prvkey))
    pubkey = bytes_from_point(mult(prvkey))
    return Script(
        [[0x00, OP_PUSHDATA, pubkey], [0x01, OP_PUSHDATA, sig]]
    )  # push signature
Ejemplo n.º 10
0
from btclib.curve import mult
from btclib.curve import secp256k1 as ec
from btclib.ssa import _batch_verify, _sign, _verify

random.seed(42)

hsize = hf().digest_size
hlen = hsize * 8

# n = 1 loops forever and does not really test batch verify
n_sig = [4, 8, 16, 32, 64, 128, 256, 512]
m = [
    random.getrandbits(hlen).to_bytes(hsize, "big") for _ in range(max(n_sig))
]
q = [random.getrandbits(ec.nlen) % ec.n for _ in m]
sig = [_sign(msg, qq) for msg, qq in zip(m, q)]
Q = [mult(qq, ec.G)[0] for qq in q]

for n in n_sig:

    # no batch
    start = time.time()
    for j in range(n):
        assert _verify(m[j], Q[j], sig[j])
    elapsed1 = time.time() - start

    # batch
    ms = m[:n]
    Qs = Q[:n]
    sigs = sig[:n]
    start = time.time()