In a January 15 2016 blog post, Vitalik mentions:
Ring signatures are more mathematically involved than simple
signatures, but they are quite practical to implement; some sample
code for ring signatures on top of Ethereum can be found here.
Here is a snippet:
def verify(msgHash:bytes32, x0:uint256, s:uint256[], Ix:uint256, Iy:uint256, pub_xs:uint256[], pub_ys:bytes):
# Number of pubkeys
n = len(pub_xs)
# Decompress the provided I value
Iy = recover_y(Ix, Iy)
# Store the list of intermediate values in the "ring"
e = array(n + 1)
# Set the first value in the ring to that provided in the signature
e[0] = [x0, sha3(x0)]
i = 1
while i < n + 1:
prev_i = (i - 1) % n
# Decompress the public key
pub_yi = recover_y(pub_xs[i % n], bit(pub_ys, i % n))
# Create the next values in the ring based on the provided s value
k1 = ecmul(Gx, Gy, 1, s[prev_i])
k2 = ecmul(pub_xs[i % n], pub_yi, 1, e[prev_i][1])
pub1 = decompose(ecsubtract(k1, k2))
k3 = self.hash_pubkey_to_pubkey([pub_xs[i % n], pub_yi], outitems=2)
k4 = ecmul(k3[0], k3[1], 1, s[prev_i])
k5 = ecmul(Ix, Iy, 1, e[prev_i][1])
pub2 = decompose(ecsubtract(k4, k5))
left = sha3([msgHash, pub1[0], pub1[1], pub2[0], pub2[1]]:arr)
right = sha3(left)
# FOR DEBUGGING
# if i >= 1:
# log(type=PubkeyLogEvent, pub_xs[i], pub_yi)
# log(type=PubkeyLogEvent, pub1[0], pub1[1])
# log(type=PubkeyLogEvent, pub2[0], pub2[1])
# log(type=ValueLogEvent, left)
# log(type=ValueLogEvent, right)
e[i] = [left, right]
i += 1
# Check that the ring is consistent
return((e[n][0] == e[0][0] and e[n][1] == e[0][1]):bool)
Note, the top of the file does say "TOTALLY NOT TESTED AND LIKELY BROKEN AT THIS POINT; AWAITING A TEST SUITE".
EDIT by user @bleepbloop:
Vitalik has confirmed that this is a very early PoC and not fully functional at present.
Looking at the code again, I think I = Ix, Iy is the 'tag' that makes this scheme linkable, there just isn't yet a function to store and compare the I values that have been previously seen, which would be needed in order to prevent double spending/double withdrawals.