Beispiel #1
0
def ecdh_encdec(masked, receiver_sk=None, derivation=None, v2=False, enc=True, dest=None):
    """
    Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a
    where C= aG + bH
    """
    rv = xmrtypes.EcdhTuple() if dest is None else dest
    if derivation is None:
        derivation = crypto.scalarmult(masked.senderPk, receiver_sk)

    if v2:
        amnt = masked.amount
        rv.mask = monero.commitment_mask(derivation)
        rv.amount = bytearray(32)
        crypto.encodeint_into(rv.amount, amnt)
        crypto.xor8(rv.amount, monero.ecdh_hash(derivation))
        rv.amount = crypto.decodeint(rv.amount)
        return rv

    else:
        amount_key_hash_single = crypto.hash_to_scalar(derivation)
        amount_key_hash_double = crypto.hash_to_scalar(
            crypto.encodeint(amount_key_hash_single)
        )

        sc_fnc = crypto.sc_add if enc else crypto.sc_sub
        rv.mask = sc_fnc(masked.mask, amount_key_hash_single)
        rv.amount = sc_fnc(masked.amount, amount_key_hash_double)
        return rv
Beispiel #2
0
def ver_asnl(P1, P2, L1, s2, s):
    """
    Aggregate Schnorr Non-Linkable
    :param P1:
    :param P2:
    :param L1:
    :param s2:
    :param s:
    :return:
    """
    logger.info("Verifying Aggregate Schnorr Non-linkable Ring Signature")

    n = len(P1)
    LHS = crypto.scalarmult_base(crypto.sc_0())
    RHS = crypto.scalarmult_base(s)
    for j in range(0, n):
        c2 = crypto.hash_to_scalar(crypto.encodepoint(L1[j]))
        L2 = crypto.point_add(crypto.scalarmult_base(s2[j]),
                              crypto.scalarmult(P2[j], c2))
        LHS = crypto.point_add(LHS, L1[j])
        c1 = crypto.hash_to_scalar(crypto.encodepoint(L2))
        RHS = crypto.point_add(RHS, crypto.scalarmult(P1[j], c1))

    if crypto.point_eq(LHS, RHS):
        return 1
    else:
        logger.warning("Didn't verify L1: %s, L1p: %s" %
                       (crypto.encodepoint(LHS), crypto.encodepoint(RHS)))
        return 0
Beispiel #3
0
    def unblind_int(self, v, k, AKout):
        AKout = crypto.hash_to_scalar(AKout)
        k = crypto.sc_sub(crypto.decodeint(k), AKout)

        AKout = crypto.hash_to_scalar(crypto.encodeint(AKout))
        v = crypto.sc_sub(crypto.decodeint(v), AKout)

        return v, k, AKout
Beispiel #4
0
def ecdh_encode_into(dst, unmasked, derivation=None):
    """
    Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a
    where C= aG + bH
    """
    sec1 = crypto.hash_to_scalar(derivation)
    sec2 = crypto.hash_to_scalar(crypto.encodeint(sec1))

    dst.mask = crypto.sc_add(unmasked.mask, sec1)
    dst.amount = crypto.sc_add(unmasked.amount, sec2)
    return dst
Beispiel #5
0
    def poc2(self):
        print('[+] PoC Ledger-app-Monero 1.4.2 spend key extraction, v2')
        self.reset()
        self.set_mode()
        self.open_tx()

        # 1. get A, find x, s.t.: [8*a*x*G]_pt == [8*a*x*G]_sc
        A = self.scalarmult(self.fake_a)
        Apt = crypto.decodepoint(A)
        x, A8x = self.find_confusion(Apt)
        Gx = crypto.encodepoint(crypto.scalarmult_base(crypto.sc_init(x)))

        print('  1. Confusion found, x: %d' % (x, ))
        print('     8xA:  %s' % binascii.hexlify(A8x).decode('ascii'))
        print('       A:  %s' % binascii.hexlify(A).decode('ascii'))
        print('      xG:  %s' % binascii.hexlify(Gx).decode('ascii'))

        # 2. gen_deriv (8*a*x*G) = enc(8x*A) = enc(P); we know {P, enc(P)};
        # It holds that P=8xA is also a valid scalar value, from the step above.
        P = self.gen_derivation(Gx, self.fake_a)
        print('  2.   P:  %s' % (self.fmtkey(P).decode('ascii'), ))

        # 3. get_secret_key: s1 = Hs(P||0) + s
        sp = self.derive_secret_key(P, 0, self.fake_b)
        print('  3.   sp: %s' % (self.fmtkey(sp).decode('ascii'), ))

        # 4. mlsag_hash(p2=1, opt=0x80) = c
        c = self.mlsag_hash()
        print('  4.   c:  %s' % (binascii.hexlify(c).decode('ascii'), ))

        # 5. mlsag_sign(s1, enc(P)), r1 = enc(s1 - Pc) = enc(Hs(P||0) + s - Pc);
        # We have R = Hs(P||0) + s - Pc -> R - Hs(P||0) + Pc = s
        r = self.mlsag_sign_s(P, sp)
        print('  5.   r:  %s' % (binascii.hexlify(r[0]).decode('ascii'), ))

        # Extract the spend key
        hs0 = crypto.hash_to_scalar(bytearray(A8x) + bytearray(1))
        rsc = crypto.decodeint(r[0])
        rsc = crypto.sc_sub(rsc, hs0)
        bsc = crypto.sc_add(
            rsc, crypto.sc_mul(crypto.decodeint(c), crypto.decodeint(A8x)))
        b = crypto.encodeint(bsc)
        print('  5.   b:  %s' % binascii.hexlify(b).decode('ascii'))

        B = crypto.scalarmult_base(bsc)
        print('  5.   B:  %s' %
              binascii.hexlify(crypto.encodepoint(B)).decode('ascii'))

        # 6. Verify
        BB = self.scalarmult(self.fake_b)
        print('  6.   bG: %s\n' % binascii.hexlify(BB).decode('ascii'))

        if BB == crypto.encodepoint(B):
            print('[+] PoC successful')
        else:
            print('[-] PoC not working')

        print('\nCommands: ')
        for x in self.commands:
            print('  %s' % x)
Beispiel #6
0
def check_ring_singature(prefix_hash, image, pubs, sig):
    """
    Checks ring signature generated with generate_ring_signature
    :param prefix_hash:
    :param image:
    :param pubs:
    :param sig:
    :return:
    """
    image_unp = crypto.ge_frombytes_vartime(image)
    image_pre = crypto.ge_dsm_precomp(image_unp)

    buff = b"" + prefix_hash
    sum = crypto.sc_0()
    for i in range(len(pubs)):
        if crypto.sc_check(sig[i][0]) != 0 or crypto.sc_check(sig[i][1]) != 0:
            return False

        tmp3 = crypto.ge_frombytes_vartime(pubs[i])
        tmp2 = crypto.ge_double_scalarmult_base_vartime(
            sig[i][0], tmp3, sig[i][1])
        buff += crypto.encodepoint(tmp2)
        tmp3 = crypto.hash_to_ec(crypto.encodepoint(pubs[i]))
        tmp2 = crypto.ge_double_scalarmult_precomp_vartime(
            sig[i][1], tmp3, sig[i][0], image_pre)
        buff += crypto.encodepoint(tmp2)
        sum = crypto.sc_add(sum, sig[i][0])

    h = crypto.hash_to_scalar(buff)
    h = crypto.sc_sub(h, sum)
    return crypto.sc_isnonzero(h) == 0
Beispiel #7
0
def check_ring_singature(prefix_hash, image, pubs, sig):
    """
    Checks ring signature generated with generate_ring_signature
    """
    image_unp = crypto.ge_frombytes_vartime(image)
    image_pre = crypto.ge_dsm_precomp(image_unp)

    buff_off = len(prefix_hash)
    buff = bytearray(buff_off + 2 * 32 * len(pubs))
    memcpy(buff, 0, prefix_hash, 0, buff_off)
    mvbuff = memoryview(buff)

    sum = crypto.sc_0()
    for i in range(len(pubs)):
        if crypto.sc_check(sig[i][0]) != 0 or crypto.sc_check(sig[i][1]) != 0:
            return False

        tmp3 = crypto.ge_frombytes_vartime(pubs[i])
        tmp2 = crypto.ge_double_scalarmult_base_vartime(
            sig[i][0], tmp3, sig[i][1])
        crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2)
        buff_off += 32

        tmp3 = crypto.hash_to_point(crypto.encodepoint(pubs[i]))
        tmp2 = crypto.ge_double_scalarmult_precomp_vartime(
            sig[i][1], tmp3, sig[i][0], image_pre)
        crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2)
        buff_off += 32

        sum = crypto.sc_add(sum, sig[i][0])

    h = crypto.hash_to_scalar(buff)
    h = crypto.sc_sub(h, sum)
    return crypto.sc_isnonzero(h) == 0
Beispiel #8
0
def ecdh_decode(masked, receiver_sk=None, derivation=None):
    """
    Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a
    where C= aG + bH
    """
    rv = xmrtypes.EcdhTuple()

    if derivation is None:
        derivation = crypto.scalarmult(masked.senderPk, receiver_sk)

    sharedSec1 = crypto.hash_to_scalar(derivation)
    sharedSec2 = crypto.hash_to_scalar(crypto.encodeint(sharedSec1))

    rv.mask = crypto.sc_sub(masked.mask, sharedSec1)
    rv.amount = crypto.sc_sub(masked.amount, sharedSec2)
    return rv
Beispiel #9
0
def ver_borromean(P1, P2, s0, s1, ee):
    """
    Verify range proof signature, Borromean
    (c.f. gmax/andytoshi's paper)
    :param P1:
    :param P2:
    :param s0:
    :param s1:
    :param ee:
    :return:
    """
    n = len(P1)
    Lv1 = key_vector(n)
    for ii in range(n):
        LL = crypto.add_keys2(s0[ii], ee, P1[ii])
        chash = crypto.hash_to_scalar(crypto.encodepoint(LL))
        Lv1[ii] = crypto.add_keys2(s1[ii], chash, P2[ii])

    kck = crypto.get_keccak()
    for ii in range(n):
        kck.update(crypto.encodepoint(Lv1[ii]))

    # ee_computed = crypto.hash_to_scalar(crypto.encodepoint(Lv1))
    ee_computed = crypto.decodeint(kck.digest())

    return crypto.sc_eq(ee_computed, ee)
Beispiel #10
0
    async def blind(self):
        AKout = self._fetch_decrypt(32)
        k = self._fetch(32)
        v = self._fetch(32)

        self.ctx_amount.update(AKout)
        self.ctx_amount.update(k)
        self.ctx_amount.update(v)

        AKout = crypto.hash_to_scalar(AKout)
        k = crypto.sc_add(crypto.decodeint(k), AKout)

        AKout = crypto.hash_to_scalar(crypto.encodeint(AKout))
        v = crypto.sc_add(crypto.decodeint(v), AKout)

        self._insert(crypto.encodeint(v))
        self._insert(crypto.encodeint(k))
        return SW_OK
Beispiel #11
0
 def test_hash_to_scalar(self):
     inp = unhexlify(
         b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405"
     )
     res = crypto.hash_to_scalar(inp)
     exp = crypto.decodeint(
         binascii.unhexlify(
             b"9907925b254e12162609fc0dfd0fef2aa4d605b0d10e6507cac253dd31a3ec06"
         ))
     self.assertTrue(crypto.sc_eq(res, exp))
Beispiel #12
0
def ecdh_encode(unmasked, receiver_pk=None, derivation=None):
    """
    Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a
    where C= aG + bH
    :param unmasked:
    :param receiver_pk:
    :param derivation:
    :return:
    """
    rv = xmrtypes.EcdhTuple()
    if derivation is None:
        esk = crypto.random_scalar()
        rv.senderPk = crypto.scalarmult_base(esk)
        derivation = crypto.encodepoint(crypto.scalarmult(receiver_pk, esk))

    sec1 = crypto.hash_to_scalar(derivation)
    sec2 = crypto.hash_to_scalar(crypto.encodeint(sec1))

    rv.mask = crypto.sc_add(unmasked.mask, sec1)
    rv.amount = crypto.sc_add(unmasked.amount, sec2)
    return rv
Beispiel #13
0
def gen_borromean(x, P1, P2, indices):
    """
    Generates Borromean signature for range proofs.
    :return:
    """
    n = len(P1)
    alpha = key_zero_vector(n)
    s1 = key_zero_vector(n)
    kck = crypto.get_keccak()  # ee computation

    for ii in range(n):
        alpha[ii] = crypto.random_scalar()
        L = crypto.scalarmult_base(alpha[ii])

        if indices[ii] == 0:
            s1[ii] = crypto.random_scalar()
            c = crypto.hash_to_scalar(crypto.encodepoint(L))
            L = crypto.add_keys2(s1[ii], c, P2[ii])
            kck.update(crypto.encodepoint(L))

        else:
            kck.update(crypto.encodepoint(L))

    ee = crypto.decodeint(kck.digest())
    del kck

    s0 = key_zero_vector(n)

    for jj in range(n):
        if not indices[jj]:
            s0[jj] = crypto.sc_mulsub(x[jj], ee, alpha[jj])
        else:
            s0[jj] = crypto.random_scalar()
            LL = crypto.add_keys2(s0[jj], ee, P1[jj])
            cc = crypto.hash_to_scalar(crypto.encodepoint(LL))
            s1[jj] = crypto.sc_mulsub(x[jj], cc, alpha[jj])

    return s0, s1, ee
Beispiel #14
0
    def poc(self):
        print('[+] PoC Ledger-app-Monero 1.4.2 spend key extraction')
        self.reset()
        self.set_mode()
        self.open_tx()

        # 1. call `monero_apdu_generate_keypair()`, obtain `{enc(x), hmac(enx(x))}`, where `x` is unknown.
        _, sec = self.gen_kp()
        print('  1. rsec: %s' % self.fmtkey(sec).decode('ascii'))

        # 2. call `sc_sub(enc(x), hmac(enc(x)), enc(x), hmac(enc(x)))`, obtain `{enc(0), hmac(enc(0))}`
        zero = self.sc_sub(sec, sec)
        print('  2. zero: %s' % self.fmtkey(zero).decode('ascii'))

        # 3. call `monero_apdu_derive_secret_key(
        #             enc(0), hmac(enc(0)), 0, C_FAKE_SEC_SPEND_KEY, hmac(C_FAKE_SEC_SPEND_KEY))`,
        #    obtain `{enc(r), hmac(enc(r))}`
        encr = self.derive_secret_key(zero, 0, self.fake_b)
        print('  3. encr: %s' % self.fmtkey(encr).decode('ascii'))

        # 4. call `monero_apdu_mlsag_sign(enc(0), hmac(enc(0)), enc(r), hmac(enc(r)))`, obtain `r`
        r = self.mlsag_sign_s(zero, encr)
        print('  4. r:  %s' % self.fmtkey(r).decode('ascii'))

        # 5. compute b: `b = r - H_s(00....00 || varint(0))`
        hs0 = crypto.hash_to_scalar(bytearray(33))
        rsc = crypto.decodeint(r[0])
        bsc = crypto.sc_sub(rsc, hs0)
        b = crypto.encodeint(bsc)
        print('  5. b:  %s' % binascii.hexlify(b).decode('ascii'))

        B = crypto.scalarmult_base(bsc)
        print('  5. B:  %s' %
              binascii.hexlify(crypto.encodepoint(B)).decode('ascii'))

        # 6. Verify
        BB = self.scalarmult(self.fake_b)
        print('  6. bG: %s' % binascii.hexlify(BB).decode('ascii'))

        if BB == crypto.encodepoint(B):
            print('[+] PoC successful')
        else:
            print('[-] PoC not working')

        if self.lite:
            return

        self.extra_poc(zero, hs0, B)
Beispiel #15
0
 def test_hash_to_scalar(self):
     inp = bytes(
         [
             0x25,
             0x9e,
             0xf2,
             0xab,
             0xa8,
             0xfe,
             0xb4,
             0x73,
             0xcf,
             0x39,
             0x05,
             0x8a,
             0x0f,
             0xe3,
             0x0b,
             0x9f,
             0xf6,
             0xd2,
             0x45,
             0xb4,
             0x2b,
             0x68,
             0x26,
             0x68,
             0x7e,
             0xbd,
             0x6b,
             0x63,
             0x12,
             0x8a,
             0xff,
             0x64,
             0x05,
         ]
     )
     res = crypto.hash_to_scalar(inp)
     exp = self.init_sc(
         0x6eca331dd53c2ca07650ed1b005d6a42aef0ffd0dfc092616124e255b920799
     )
     self.assertTrue(crypto.sc_eq(res, exp))
Beispiel #16
0
def get_subaddress_secret_key(
    secret_key, index=None, major=None, minor=None, little_endian=True
):
    """
    Builds subaddress secret key from the subaddress index
    Hs(SubAddr || a || index_major || index_minor)

    UPDATE: Monero team fixed this problem. Always use little endian.
    Note: need to handle endianity in the index
    C-code simply does: memcpy(data + sizeof(prefix) + sizeof(crypto::secret_key), &index, sizeof(subaddress_index));
    Where the index has the following form:

    struct subaddress_index {
        uint32_t major;
        uint32_t minor;
    }

    https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment
    :param secret_key:
    :param index:
    :param major:
    :param minor:
    :param little_endian:
    :return:
    """
    if index:
        major = index.major
        minor = index.minor
    endianity = "<" if little_endian else ">"
    prefix = b"SubAddr"
    buffer = bytearray(len(prefix) + 1 + 32 + 4 + 4)
    struct.pack_into(
        "%s7sb32sLL" % endianity,
        buffer,
        0,
        prefix,
        0,
        crypto.encodeint(secret_key),
        major,
        minor,
    )
    return crypto.hash_to_scalar(buffer)
Beispiel #17
0
def generate_ring_signature(prefix_hash,
                            image,
                            pubs,
                            sec,
                            sec_idx,
                            test=False):
    """
    Generates ring signature with key image.
    void crypto_ops::generate_ring_signature()
    """
    if test:
        t = crypto.scalarmult_base(sec)
        if not crypto.point_eq(t, pubs[sec_idx]):
            raise ValueError("Invalid sec key")

        k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec)
        if not crypto.point_eq(k_i, image):
            raise ValueError("Key image invalid")
        for k in pubs:
            crypto.ge_frombytes_vartime_check(k)

    image_unp = crypto.ge_frombytes_vartime(image)
    image_pre = crypto.ge_dsm_precomp(image_unp)

    buff_off = len(prefix_hash)
    buff = bytearray(buff_off + 2 * 32 * len(pubs))
    memcpy(buff, 0, prefix_hash, 0, buff_off)
    mvbuff = memoryview(buff)

    sum = crypto.sc_0()
    k = crypto.sc_0()
    sig = []
    for i in range(len(pubs)):
        sig.append([crypto.sc_0(), crypto.sc_0()])  # c, r

    for i in range(len(pubs)):
        if i == sec_idx:
            k = crypto.random_scalar()
            tmp3 = crypto.scalarmult_base(k)
            crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp3)
            buff_off += 32

            tmp3 = crypto.hash_to_point(crypto.encodepoint(pubs[i]))
            tmp2 = crypto.scalarmult(tmp3, k)
            crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2)
            buff_off += 32

        else:
            sig[i] = [crypto.random_scalar(), crypto.random_scalar()]
            tmp3 = crypto.ge_frombytes_vartime(pubs[i])
            tmp2 = crypto.ge_double_scalarmult_base_vartime(
                sig[i][0], tmp3, sig[i][1])
            crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2)
            buff_off += 32

            tmp3 = crypto.hash_to_point(crypto.encodepoint(tmp3))
            tmp2 = crypto.ge_double_scalarmult_precomp_vartime(
                sig[i][1], tmp3, sig[i][0], image_pre)
            crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2)
            buff_off += 32

            sum = crypto.sc_add(sum, sig[i][0])

    h = crypto.hash_to_scalar(buff)
    sig[sec_idx][0] = crypto.sc_sub(h, sum)
    sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k)
    return sig
Beispiel #18
0
def prove_range_mem(amount, last_mask=None):
    """
    Memory optimized range proof.

    Gives C, and mask such that \sumCi = C
    c.f. http:#eprint.iacr.org/2015/1098 section 5.1

    Ci is a commitment to either 0 or 2^i, i=0,...,63
    thus this proves that "amount" is in [0, 2^ATOMS]
    mask is a such that C = aG + bH, and b = amount
    :param amount:
    :param last_mask: ai[ATOMS-1] will be computed as \sum_{i=0}^{ATOMS-2} a_i - last_mask
    :return: sumCi, mask, RangeSig.
        sumCi is Pedersen commitment on the amount value. sumCi = aG + amount*H
        mask is "a" from the Pedersent commitment above.
    """
    n = ATOMS
    bb = d2b(amount, n)  # gives binary form of bb in "digits" binary digits
    ai = [None] * len(bb)
    Ci = [None] * len(bb)
    a = crypto.sc_0()

    C = crypto.identity()
    alpha = mlsag2.key_zero_vector(n)
    s1 = mlsag2.key_zero_vector(n)
    c_H = crypto.xmr_H()
    kck = crypto.get_keccak()  # ee computation

    # First pass, generates: ai, alpha, Ci, ee, s1
    for ii in range(n):
        ai[ii] = crypto.random_scalar()
        if last_mask is not None and ii == ATOMS - 1:
            ai[ii] = crypto.sc_sub(last_mask, a)

        a = crypto.sc_add(
            a, ai[ii]
        )  # creating the total mask since you have to pass this to receiver...

        alpha[ii] = crypto.random_scalar()
        L = crypto.scalarmult_base(alpha[ii])

        if bb[ii] == 0:
            Ci[ii] = crypto.scalarmult_base(ai[ii])
        else:
            Ci[ii] = crypto.point_add(crypto.scalarmult_base(ai[ii]), c_H)
        C = crypto.point_add(C, Ci[ii])

        if bb[ii] == 0:
            s1[ii] = crypto.random_scalar()
            c = crypto.hash_to_scalar(crypto.encodepoint(L))
            L = crypto.add_keys2(s1[ii], c, crypto.point_sub(Ci[ii], c_H))
            kck.update(crypto.encodepoint(L))

        else:
            kck.update(crypto.encodepoint(L))

        c_H = crypto.point_double(c_H)

    # Compute ee, memory cleanup
    ee = crypto.decodeint(kck.digest())
    del kck

    # Second phase computes: s0, s1
    c_H = crypto.xmr_H()
    s0 = mlsag2.key_zero_vector(n)

    for jj in range(n):
        if not bb[jj]:
            s0[jj] = crypto.sc_mulsub(ai[jj], ee, alpha[jj])

        else:
            s0[jj] = crypto.random_scalar()
            LL = crypto.add_keys2(s0[jj], ee, Ci[jj])
            cc = crypto.hash_to_scalar(crypto.encodepoint(LL))
            s1[jj] = crypto.sc_mulsub(ai[jj], cc, alpha[jj])
        c_H = crypto.point_double(c_H)

    A = xmrtypes.BoroSig()
    A.s0, A.s1, A.ee = s0, s1, ee

    R = xmrtypes.RangeSig()
    R.asig = A
    R.Ci = Ci

    return C, a, R
Beispiel #19
0
def generate_ring_signature(prefix_hash,
                            image,
                            pubs,
                            sec,
                            sec_idx,
                            test=False):
    """
    Generates ring signature with key image.
    void crypto_ops::generate_ring_signature()

    :param prefix_hash:
    :param image:
    :param pubs:
    :param sec:
    :param sec_idx:
    :param test:
    :return:
    """
    if test:
        t = crypto.scalarmult_base(sec)
        if not crypto.point_eq(t, pubs[sec_idx]):
            raise ValueError("Invalid sec key")

        k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec)
        if not crypto.point_eq(k_i, image):
            raise ValueError("Key image invalid")
        for k in pubs:
            crypto.ge_frombytes_vartime_check(k)

    image_unp = crypto.ge_frombytes_vartime(image)
    image_pre = crypto.ge_dsm_precomp(image_unp)

    buff = b"" + prefix_hash
    sum = crypto.sc_0()
    k = crypto.sc_0()
    sig = []
    for i in range(len(pubs)):
        sig.append([crypto.sc_0(), crypto.sc_0()])  # c, r

    for i in range(len(pubs)):
        if i == sec_idx:
            k = crypto.random_scalar()
            tmp3 = crypto.scalarmult_base(k)
            buff += crypto.encodepoint(tmp3)
            tmp3 = crypto.hash_to_ec(crypto.encodepoint(pubs[i]))
            tmp2 = crypto.scalarmult(tmp3, k)
            buff += crypto.encodepoint(tmp2)
        else:
            sig[i] = [crypto.random_scalar(), crypto.random_scalar()]
            tmp3 = crypto.ge_frombytes_vartime(pubs[i])
            tmp2 = crypto.ge_double_scalarmult_base_vartime(
                sig[i][0], tmp3, sig[i][1])
            buff += crypto.encodepoint(tmp2)
            tmp3 = crypto.hash_to_ec(crypto.encodepoint(tmp3))
            tmp2 = crypto.ge_double_scalarmult_precomp_vartime(
                sig[i][1], tmp3, sig[i][0], image_pre)
            buff += crypto.encodepoint(tmp2)
            sum = crypto.sc_add(sum, sig[i][0])

    h = crypto.hash_to_scalar(buff)
    sig[sec_idx][0] = crypto.sc_sub(h, sum)
    sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k)
    return sig