mirror of
https://github.com/Rucknium/misc-research.git
synced 2025-01-03 17:19:30 +00:00
235 lines
7.5 KiB
R
235 lines
7.5 KiB
R
|
|
|
|
|
|
|
|
CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE = 10
|
|
DIFFICULTY_TARGET_V2 = 120
|
|
DEFAULT_UNLOCK_TIME = CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2
|
|
RECENT_SPEND_WINDOW = 15 * DIFFICULTY_TARGET_V2
|
|
|
|
SECONDS_IN_A_YEAR = 60 * 60 * 24 * 365
|
|
BLOCKS_IN_A_YEAR = SECONDS_IN_A_YEAR / DIFFICULTY_TARGET_V2
|
|
|
|
|
|
|
|
|
|
calculate_average_output_flow <- function(crod) {
|
|
# 1
|
|
num_blocks_to_consider_for_flow = min(c(length(crod), BLOCKS_IN_A_YEAR))
|
|
|
|
# 2
|
|
if (length(crod) > num_blocks_to_consider_for_flow) {
|
|
num_outputs_to_consider_for_flow = crod[length(crod)] - crod[ length(crod) - num_blocks_to_consider_for_flow ]
|
|
# R indexes from 1
|
|
} else {
|
|
num_outputs_to_consider_for_flow = crod[length(crod)] # R indexes from 1
|
|
}
|
|
|
|
# 3
|
|
average_output_flow = DIFFICULTY_TARGET_V2 * num_blocks_to_consider_for_flow / num_outputs_to_consider_for_flow
|
|
|
|
return(average_output_flow)
|
|
}
|
|
|
|
calculate_num_usable_rct_outputs <- function(crod) {
|
|
# 1
|
|
num_usable_crod_blocks = length(crod) - (CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE - 1)
|
|
|
|
# 2
|
|
num_usable_rct_outputs = crod[num_usable_crod_blocks] # R indexes from 1
|
|
|
|
return(num_usable_rct_outputs)
|
|
}
|
|
|
|
|
|
|
|
|
|
GAMMA_SHAPE = 19.28
|
|
GAMMA_RATE = 1.61
|
|
# GAMMA_SCALE = 1 / GAMMA_RATE
|
|
|
|
|
|
G <- function(x) {
|
|
actuar::plgamma(x, shapelog = GAMMA_SHAPE, ratelog = GAMMA_RATE)
|
|
}
|
|
|
|
|
|
|
|
|
|
crod <- xmr.rpc(url.rpc = paste0(url.rpc, "/json_rpc"), method = "get_output_distribution",
|
|
params = list(amounts = list(0), from_height = 0, to_height = current.height, binary = FALSE, cumulative = TRUE))
|
|
|
|
|
|
|
|
start_height <- crod$result$distributions[[1]]$start_height
|
|
crod <- crod$result$distributions[[1]]$distribution
|
|
crod.full <- crod
|
|
|
|
|
|
spam.output_index <- list()
|
|
|
|
for (i in seq_along(spam.results)) {
|
|
spam.output_index[[i]] <- list(name = spam.types[[i]]$fingerprint.text,
|
|
output_index = spam.results[[i]]$spam.fingerprint$output_index)
|
|
}
|
|
|
|
|
|
n.workers <- min(floor(parallelly::availableCores()/2), 32L)
|
|
|
|
future::plan(future::multisession(workers = n.workers))
|
|
|
|
|
|
adversary.owned.dsa.mass <- future.apply::future_lapply((start.spam.height:current.height), function(ring.construction.height) {
|
|
|
|
crod <- crod.full[1:(ring.construction.height - start_height + 1)]
|
|
|
|
average_output_flow <- calculate_average_output_flow(crod)
|
|
|
|
num_usable_rct_outputs <- calculate_num_usable_rct_outputs(crod)
|
|
|
|
|
|
v <- average_output_flow
|
|
z <- num_usable_rct_outputs
|
|
|
|
|
|
G_star <- function(x) {
|
|
(0 <= x*v & x*v <= 1800) *
|
|
(G(x*v + 1200) - G(1200) +
|
|
( (x*v)/(1800) ) * G(1200)
|
|
)/G(z*v + 1200) +
|
|
(x*v > 1800) * G(x*v + 1200)/G(z*v + 1200)
|
|
}
|
|
|
|
|
|
usable.outputs <- 1:num_usable_rct_outputs
|
|
|
|
crod.reversed <- cumsum(abs(diff(rev(crod)))[-(1:9)])
|
|
# Remove first 9 blocks before cumsum() since cant spend from those outputs
|
|
|
|
crod.reversed <- c(0, crod.reversed)
|
|
|
|
y_0 <- crod.reversed[-length(crod.reversed)] + 1
|
|
y_1 <- crod.reversed[-1]
|
|
pmf.decoy.crod <- (G_star(y_1 + 1) - G_star(y_0)) / (y_1 + 1 - y_0)
|
|
|
|
|
|
pmf.decoy <- rep(pmf.decoy.crod, times = diff(crod.reversed))
|
|
|
|
|
|
|
|
pmf.decoy.reversed <- rev(pmf.decoy)
|
|
|
|
result <- list()
|
|
|
|
for (i in seq_along(spam.output_index)) {
|
|
|
|
estimated.adversary.owned.share <- sum(pmf.decoy.reversed[
|
|
spam.output_index[[i]]$output_index[ spam.output_index[[i]]$output_index <= length(pmf.decoy.reversed)] ])
|
|
|
|
result[[i]] <- data.table(ring.construction.height = ring.construction.height,
|
|
estimated.adversary.owned.share = estimated.adversary.owned.share,
|
|
type = spam.output_index[[i]]$name)
|
|
|
|
}
|
|
|
|
rbindlist(result)
|
|
|
|
})
|
|
|
|
|
|
|
|
adversary.owned.dsa.mass <- rbindlist(adversary.owned.dsa.mass)
|
|
|
|
adversary.owned.dsa.mass <- merge(adversary.owned.dsa.mass, block.data[, .(height, timestamp.POSIX)],
|
|
by.x = "ring.construction.height", by.y = "height")
|
|
|
|
setorder(adversary.owned.dsa.mass, timestamp.POSIX)
|
|
|
|
adversary.owned.dsa.mass[, effective.ring.size := 1 + (1 - estimated.adversary.owned.share) * 15]
|
|
|
|
|
|
png("empirical-effective-ring-size.png", width = 800, height = 800)
|
|
|
|
ggplot(adversary.owned.dsa.mass, aes(x = timestamp.POSIX, y = effective.ring.size, colour = type)) +
|
|
geom_line() +
|
|
scale_y_continuous(breaks = 1:16, limits = c(0, NA), expand = c(0, 0)) +
|
|
scale_x_datetime(date_breaks = "day", guide = guide_axis(angle = 90)) +
|
|
ggtitle("Estimated mean effective ring size") +
|
|
xlab(" Date github.com/Rucknium") +
|
|
ylab("Mean effective ring size") +
|
|
labs(colour = "Spam type") +
|
|
theme(legend.position = "top", legend.text = element_text(size = 15), legend.title = element_text(size = 15),
|
|
plot.title = element_text(size = 20),
|
|
plot.subtitle = element_text(size = 15),
|
|
axis.text = element_text(size = 15),
|
|
axis.title.x = element_text(size = 15, margin = margin(t = 10)),
|
|
axis.title.y = element_text(size = 15), strip.text = element_text(size = 15)) +
|
|
guides(colour = guide_legend(override.aes = list(linewidth = 5)))
|
|
|
|
dev.off()
|
|
|
|
|
|
|
|
|
|
guess.prob <- function(effective.ring.size, nominal.ring.size) {
|
|
decoys <- nominal.ring.size - 1
|
|
sapply(effective.ring.size, FUN = function(x) {
|
|
weighted.mean(1/(1 + 0:decoys),
|
|
w = dbinom(0:decoys, size = decoys, prob = (x - 1)/decoys))
|
|
})
|
|
}
|
|
|
|
|
|
|
|
adversary.owned.dsa.mass[, guess.prob := guess.prob(effective.ring.size, nominal.ring.size = 16)]
|
|
|
|
png("empirical-guessing-probability.png", width = 800, height = 800)
|
|
|
|
ggplot(adversary.owned.dsa.mass, aes(x = timestamp.POSIX, y = guess.prob, colour = type)) +
|
|
geom_line() +
|
|
scale_y_continuous( limits = c(0, NA), expand = c(0, 0), labels = scales::label_percent()) +
|
|
scale_x_datetime(date_breaks = "day", guide = guide_axis(angle = 90)) +
|
|
ggtitle("Estimated probability of correctly guessing the real spend") +
|
|
xlab(" Date github.com/Rucknium") +
|
|
ylab("Probability") +
|
|
labs(colour = "Spam type") +
|
|
theme(legend.position = "top", legend.text = element_text(size = 15), legend.title = element_text(size = 15),
|
|
plot.title = element_text(size = 20),
|
|
plot.subtitle = element_text(size = 15),
|
|
axis.text = element_text(size = 15),
|
|
axis.title.x = element_text(size = 15, margin = margin(t = 10)),
|
|
axis.title.y = element_text(size = 15), strip.text = element_text(size = 15)) +
|
|
guides(colour = guide_legend(override.aes = list(linewidth = 5)))
|
|
|
|
dev.off()
|
|
|
|
|
|
|
|
adversary.owned.dsa.mass[, effective.ring.size.one := dbinom(0, size = 15, prob = 1 - estimated.adversary.owned.share)]
|
|
|
|
png("empirical-ring-size-one.png", width = 800, height = 800)
|
|
|
|
ggplot(adversary.owned.dsa.mass, aes(x = timestamp.POSIX, y = effective.ring.size.one, colour = type)) +
|
|
geom_line() +
|
|
scale_y_continuous( limits = c(0, NA), expand = c(0, 0), labels = scales::label_percent()) +
|
|
scale_x_datetime(date_breaks = "day", guide = guide_axis(angle = 90)) +
|
|
ggtitle("Estimated share of rings with effective ring size of one") +
|
|
xlab(" Date github.com/Rucknium") +
|
|
ylab("Share of rings") +
|
|
labs(colour = "Spam type") +
|
|
theme(legend.position = "top", legend.text = element_text(size = 15), legend.title = element_text(size = 15),
|
|
plot.title = element_text(size = 20),
|
|
plot.subtitle = element_text(size = 15),
|
|
axis.text = element_text(size = 15),
|
|
axis.title.x = element_text(size = 15, margin = margin(t = 10)),
|
|
axis.title.y = element_text(size = 15), strip.text = element_text(size = 15)) +
|
|
guides(colour = guide_legend(override.aes = list(linewidth = 5)))
|
|
|
|
dev.off()
|
|
|
|
future::plan(future::sequential)
|
|
# Reset to remove threaded R sessions to get back RAM
|
|
|
|
|
|
|
|
|