MP-SPDZ的学习与运用

目录

    • MP-SPDZ 的介绍
      • 主要功能
      • 典型应用场景
    • MP-SPDZ 的安装
      • 实验环境准备
      • 环境安装
      • MP-SPDZ 下载和编译
    • MP-SPDZ 的使用
      • 测试程序
      • 第三方求和
      • 三方计算
      • 测试
      • 冒泡排序
      • 比较运算函数
      • 语法详解——Sint
      • 语法详解——Array
      • 基于AES电路实现OPRF
      • ORAM
      • 隐私集合求交实现
      • 两台虚拟机之间进行MPC简单实例
      • 基础OT的用法

MP-SPDZ 的介绍

MP-SPDZ 是一个开源框架,用于实现各种安全多方计算协议。它由多个安全协议和优化组成,旨在提供高效的、安全的多方计算功能,适用于学术研究和实际应用。

主要功能

  1. 多种协议支持

    • SPDZSPDZ2k:基于同态加密和秘密共享的协议。
    • MASCOT:高效的秘密共享协议,适用于大规模计算。
    • Overdrive:增强的 SPDZ 协议,提供更高效的离线预处理。
    • Yao’s Garbled Circuits:适用于两方计算。
    • GMW:基于加密电路的协议。
    • BMR:用于安全电路计算的协议。
    • Shamir’s Secret SharingReplicated Secret Sharing:适用于诚实多数的协议。
    • Threshold ECDSA:用于门限签名的协议。
  2. 高效的编译和执行

    • 支持将高层次的程序编译为高效的字节码,并在虚拟机上执行。
    • 提供多线程支持和协议特定的优化选项。
    • 支持本地和远程执行模式。
  3. 机器学习支持

    • 支持逻辑回归和线性回归的梯度下降(SGD)训练。
    • 集成 PyTorch 和 Keras 模型,支持常见的深度学习功能。
    • 支持决策树的训练和预测。
  4. 数学和线性代数操作

    • 支持整数、浮点数和定点数的基本运算和复杂操作。
    • 支持矩阵运算,包括矩阵乘法、转置等。
  5. 输入输出操作

    • 支持从文件、套接字等多种渠道输入和输出数据。
    • 支持秘密输入和输出,确保数据的隐私性。

典型应用场景

  1. 隐私保护数据分析

    • 在不泄露各方数据隐私的前提下,进行联合数据分析和统计。
  2. 安全机器学习

    • 在多方协作下训练机器学习模型,确保训练数据的隐私。
  3. 金融数据共享

    • 银行和金融机构之间进行安全的数据交换和计算,防止数据泄露。

MP-SPDZ 的安装

实验环境准备

Ubuntu 22.04.3 LTS

环境安装

sudo apt-get install automake build-essential git libboost-dev libboost-thread-dev libntl-dev libsodium-dev libssl-dev libtool m4 python3 texinfo yasm libboost-all-dev cmake clang libgmp-dev

MP-SPDZ 下载和编译

git clone https://github.com/data61/MP-SPDZ.git
cd MP-SPDZ
make setup
Scripts/tldr.sh
make -j 8 tldr # 对spdz库进行编译

MP-SPDZ 的使用

测试程序

官方提供了一个测试程序,这个代码内容就是各种运算的小测试。

Scripts/tldr.sh
echo 1 2 3 4 > Player-Data/Input-P0-0
echo 1 2 3 4 > Player-Data/Input-P1-0
Scripts/compile-run.py -E mascot tutorial

请添加图片描述
请添加图片描述

第三方求和

在Programs/Source文件夹内新建一个源代码文件testgp.mpc(后缀名是.mpc)

vim Programs/Source/testgp.mpc

编辑内容如下:

a = sint.get_input_from(0)
b = sint.get_input_from(1)
c = sint.get_input_from(2)
sum = a + b + c
print_ln('Results =%s',sum.reveal())

运行程序

./compile.py -B 32 Programs/Source/testgp.mpc

在这里插入图片描述

三方计算

生成证书文件,3代表三方计算。

Scripts/setup-ssl.sh 3

请添加图片描述
写入三方数据

echo 11 > Player-Data/Input-P0-0
echo 12 > Player-Data/Input-P1-0
echo 13 > Player-Data/Input-P2-0

开三个新终端模拟三方计算,进行运算,得到结果:

./shamir-bmr-party.x -N 3 0 testgp
./shamir-bmr-party.x -N 3 1 testgp
./shamir-bmr-party.x -N 3 2 testgp

请添加图片描述
请添加图片描述

请添加图片描述

测试

介绍一个命令

./emulate.x <program>

可以用来执行编译好的程序。

对于一个测试样例testgp.mpc

  1. 首先编译程序:
./compile.py testgp
  1. 编译成功后直接执行:
./emulate.x testgp

就可以跑程序。

冒泡排序

  1. 编写冒泡排序算法
vim Programs/Source/merge_and_sort.mpc

from util import if_else
from Compiler import types
 
def maopao(a):
	n = len(a)
	@for_range(n)
	def _(i):
		@for_range(n)
		def _(j):
			@if_((a[i] > a[j]).reveal())
			def _():
				# print_ln("!!!")
				tmp = a[i]
				a[i] = a[j]
				a[j] = tmp
	return a
 
n = 5
 
a = Array(n, sfix)
 
@for_range_opt(n)
def _(i):
    a[i] = sfix.get_input_from(0)
 
d = maopao(a) 
print_ln('Data receive success !!')
  1. 程序解释
    首先定义了一个函数,都是python语法,实现冒泡排序

然后指定数组大小n=5。定义一个sfix类型的数组a。sfix指安全的定点数(Secret fixed-point number represented as secret integer.)。

通过for循环将参与方的数据写入数组a。其中,get_input_from(0),0代表参与方的编号(ID)(这里是0号)。

注意for循环的写法,跟python语法还是有区别的。
3. 程序编译与执行
程序编译:merge_and_sort是程序文件名,源代码放在Programs/Source/中

./compile.py -B 32 merge_and_sort

请添加图片描述
4. 生成证书与密钥文件,建立安全的通道:在这个程序里只有一方参与计算。

Scripts/setup-ssl.sh 1

请添加图片描述

  1. 输入参与方的数据:数据存储在d0.dat文件里,这里是五个数,代表一个数组。
echo 8 9 7 4 6 > Player-Data/Input-P0-0
  1. 执行程序
./emulate.x merge_and_sort

请添加图片描述

比较运算函数

小于:less_than = lt()

大于:greater_than = gt()

小于等于: less_equal = le()

大于等于: greater_equal = ge()

等于: equal = eq()

不等于: not_equal = ne()

  1. 编写程序
vim Programs/Source/compare.mpc
a = sfix(12)
b = sfix(13)
print_ln("a: %s",a.reveal())
print_ln("b: %s",b.reveal())
print_ln("less_than: %s", sfix.__lt__(a,b).reveal())
print_ln("greater_than: %s", sfix.__gt__(a,b).reveal())
print_ln("less_equal: %s", sfix.__le__(a,b).reveal())
print_ln("greater_equal: %s", sfix.__ge__(a,b).reveal())
print_ln("equal: %s", sfix.__eq__(a,b).reveal())
print_ln("not_equal: %s", sfix.__ne__(a,b).reveal())  
  1. 运行程序
./compile.py -R 128 compare
./emulate.x compare

请添加图片描述

语法详解——Sint

原博客链接

语法详解——Array

原博客链接

基于AES电路实现OPRF

  1. 生成数据脚本,该脚本会生成127比特(可以改,生成任意比特的)的随机数(十六进制表示),也就是key和plaintext。并将这些数据写入对应的数据文件。
vim gen_data.py
import random
import os,sys
 
base = [str(x) for x in range(10)] + [ chr(x) for x in range(ord('A'),ord('A')+6)]
 
# hex2dec
def hex2dec(string_num):
    return str(int(string_num.upper(), 16))
 
# dec2hex
def dec2hex(string_num):
    num = int(string_num)
    mid = []
    while True:
        if num == 0: break
        num,rem = divmod(num, 16)
        mid.append(base[rem])
 
    return '0x'+''.join([str(x) for x in mid[::-1]])
 
def gen_random_hex(n_bit):
    b = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
    s = "0x"
    n = n_bit/4
    for i in range(n):
        t = random.randint(0,15)
        s = s + b[t]
    return s
 
bit = 127
a = gen_random_hex(bit)
ha = hex2dec(a)
b = gen_random_hex(bit)
hb = hex2dec(b)
print("############ gen_data.py #############")
print("a = " + a)
print("Dec(a) = " + ha)
print("b = " + b)
print("Dec(b) = " + hb)
 
f1 = open("Player-Data/Input-P0-0", "w")
f2 = open("Player-Data/Input-P1-0", "w")
print("Write a to Player-Data/Input-P0-0...")
f1.write(str(a))
print("Write b to Player-Data/Input-P1-0...")
f2.write(str(b))
f1.close()
f2.close()
print("################ End #################")
  1. 编写程序
vim Programs/Source/aes_circuit.mpc
from circuit import Circuit
sb128 = sbits.get_type(128)
key = sb128(0x2b7e151628aed2a6abf7158809cf4f3c)
plaintext = sb128(0x6bc1bee22e409f96e93d7e117393172a)
n = 1000
aes128 = Circuit('aes_128')
ciphertexts = aes128(sbitvec([key] * n), sbitvec([plaintext] * n))
ciphertexts.elements()[n - 1].reveal().print_reg()                                               
  1. 编写脚本
#! /bin/bash
./compile.py -B 128 aes_circuit
Scripts/setup-ssl.sh 2
python3 gen_data.py
echo "Player-Data/Input-P0-0:" $(cat Player-Data/Input-P0-0)
echo "Player-Data/Input-P1-0:" $(cat Player-Data/Input-P1-0)
echo "########### Execute aes_circuit... #############"
Scripts/yao.sh aes_circuit
  1. 运行脚本
    运行报错,尚未解决
    请添加图片描述

ORAM

oram_tutorial.mpc

from oram import OptimalORAM
 
array = OptimalORAM(10000)
array[1] = 123
print_ln('%s', array[1].reveal())

编译:

./compile.py -D -R 64 oram_tutorial

运行:

./emulate.x oram_tutorial

请添加图片描述
请添加图片描述

隐私集合求交实现

vim Programs/Source/psi.mpc
from util import if_else
from Compiler import types
from Compiler import mpc_math
program.bit_length = 128
def compute_intersection(a, b):
    n = len(a)
    intersection = Array(n, sfix)
    is_match_at = Array(n, sfix)
    @for_range(n)
    def _(i):
        @for_range(n)
        def _(j):
            match = a[i] == b[j]
            is_match_at[i] += match
            intersection[i] = if_else(match, a[i], intersection[i]) 
    return intersection, is_match_at
def set_intersection_example(a,b):
    print_ln('Running PSI example')
    intersection, is_match_at = compute_intersection(a,b)
    print_ln('Printing set intersection (0: not in intersection)')
    size = MemValue(sfix(0))
    total = MemValue(sfix(0))
    @for_range(n)
    def _(i):
        size.write(size + is_match_at[i])
        total.write(total + intersection[i])
        print_str('%s ', intersection[i].reveal())
    print_ln('\nIntersection size: %s', size.reveal())
 
n = 10
a = Array(n, sfix)
b = Array(n, sfix)
@for_range_opt(n)
def _(i):
    a[i] = sfix.get_input_from(0)
@for_range_opt(n)
def _(j):
    b[j] = sfix.get_input_from(1)
print_ln('data is ok')
set_intersection_example(a,b)

编译、生成证书、写数据

./compile.py -B 64 psi
Scripts/setup-ssl.sh 2
echo 44 76 14 45 31 4 67 39 78 84 > Player-Data/Input-P0-0
echo 1864 14 44 7335 2791 564 39 9085 4 7220 > Player-Data/Input-P1-0

执行程序,要在两个终端运行:

./semi-bmr-party.x -N 2 0 psi
./semi-bmr-party.x -N 2 1 psi

请添加图片描述
请添加图片描述
请添加图片描述

两台虚拟机之间进行MPC简单实例

需要两台服务器,未完待续。。。。

基础OT的用法

useOT.cpp

#include <iostream>

#include "OT/BaseOT.h"  
#include "Networking/Player.h"
#include "Tools/octetStream.h"

using namespace std;

int main()
{
	
	
	int player = 0;
	int nplayers = 2;
	const char* servername = "127.0.0.1";
	int pnb = 8000;
	int my_port = 8001;
	Names n = Names(player,nplayers,servername,pnb,my_port);
	//Names n = Names(player,nplayers,servername,pnb,Names::DEFAULT_PORT);
	RealTwoPartyPlayer tp = RealTwoPartyPlayer(n,1,101);
	//PlainPlayer p = PlainPlayer(n,10);

	OT_ROLE ot_role = SENDER;
	//int mynum = 8000;
	
	RealTwoPartyPlayer *s;
	s = &tp;
	BaseOT send = BaseOT(8,128,s, ot_role);
	send.exec_base();

	cout<< "Success !!" <<endl; 
	return 0;
}

BaseOT.h 写了两个测试程序。第一个是OT里的发送者,第二个是接受者。

程序详解:

实例化类Names用来开启并监听端口,进行通信。
实例化类RealTwoPartyPlaye 用来模拟两个参与方。
第一个参数是Names类
第二个参数:other_player,表示另一个参与者的编号
第三个参数:唯一标志id
实例化一个BaseOT类
第一个参数nOT,表示执行OT的数量,这里我设置为8,注意必须为4的倍数,要不会报错。
第二参数表示消息的长度,这里我设置为128比特。
第三个参数,参与方的类指针
第四个参数,ot的角色,这里有三个角色:发送者(SENDER),接收者(RECEIVER),两者都是(BOTH)。
实例化时候,也可以只给第三四个参数,这时候nOT和ot_length都默认为128
调用类方法exec_base()。运行的时候只需要这些参数,所有消息和选择比特都随机生成了。
useOTRec.cpp

#include <iostream>

#include "OT/BaseOT.h"  
#include "Networking/Player.h"
#include "Tools/octetStream.h"

using namespace std;

int main()
{
	int player = 1;
	int nplayers = 2;
	const char* servername = "127.0.0.1";
	int pnb = 8000;
	int my_port = 8002;
	Names n = Names(player,nplayers,servername,pnb,my_port);
	//Names n = Names(player,nplayers,servername,pnb,Names::DEFAULT_PORT);
	RealTwoPartyPlayer tp = RealTwoPartyPlayer(n,0,101);
	//PlainPlayer p = PlainPlayer(n,10);

	OT_ROLE ot_role = RECEIVER;
	//int mynum = 8000;
	
	RealTwoPartyPlayer *s;
	s = &tp;
	BaseOT rec = BaseOT(8,128,s, ot_role);
	rec.exec_base();

	cout<< "Success !!" <<endl; 
	return 0;
}

makefile文件

# objs_SimpleOT = ../SimpleOT/ot_sender.o ../SimpleOT/ot_receiver.o ../SimpleOT/sc25519_random.o ../SimpleOT/ge25519_pack.o ../SimpleOT/ge25519_double.o ../SimpleOT/fe25519_pack.o
objs_SimpleOT = ../SimpleOT/*.o
all: useOT useOTRec
useOT: useOT.o
	g++ -o useOT useOT.o ../OT/*.o $(objs_SimpleOT) ../libSPDZ.so -lntl -lmpirxx -lmpir -lsodium -lboost_system -lssl -lcrypto -lrt

useOTRec: useOTRec.o
	g++ -o useOTRec useOTRec.o ../OT/*.o $(objs_SimpleOT) ../libSPDZ.so -lntl -lmpirxx -lmpir -lsodium -lboost_system -lssl -lcrypto -lrt

useOT.o: useOT.cpp ../Networking/Player.h ../OT/BaseOT.h
	g++ -c useOT.cpp -I ~/spdz029 -march=native -g -Wextra -Wall -O3 -I. -pthread    -DUSE_GF2N_LONG '-DPREP_DIR="Player-Data/"'  -std=c++11 -Werror -fPIC -MMD -MP -c

useOTRec.o: useOTRec.cpp ../Networking/Player.h ../OT/BaseOT.h
	g++ -c useOTRec.cpp -I ~/spdz029 -march=native -g -Wextra -Wall -O3 -I. -pthread    -DUSE_GF2N_LONG '-DPREP_DIR="Player-Data/"'  -std=c++11 -Werror -fPIC -MMD -MP -c

clean:
	rm -f *.o main *.out useOT useOTRec *.d

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/681319.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(一文读懂)大模型到底是怎么生成文字的?

前言 在人工智能的领域&#xff0c;大模型在去年已经成为了一个热门的话题。 各大厂商如谷歌、微软、OpenAI等&#xff0c;都在积极研发和应用大模型技术。 这些模型在语言理解、图像识别、推荐系统等方面都表现出了惊人的能力&#xff0c;甚至在某些任务上&#xff0c;已经…

响应式网站设计:为何成为首选及其优点

随着近 35 亿万人使用不同的设备访问互联网&#xff0c;响应性网站的设计变得越来越重要。大多数网站的宽度 720 到 1,000 像素间。假如网站访问者的显示器设置为 800 像素或更大且页面宽度超过 720 像素&#xff0c;那么必须向右滚动屏幕来显示所有的信息。目前的问题是不清楚…

Java(十)——内部类

文章目录 内部类静态内部类实例内部类匿名内部类局部内部类 内部类 Java内部类是一种特殊的类定义方式&#xff0c;它允许在一个类的内部定义另一个类。 内部类可以访问其所在外部类的成员变量和成员方法&#xff0c;这使得它非常适用于封装与外部类紧密相关的私有逻辑。 内…

深入理解API:数据的桥梁与门户

API&#xff08;应用程序编程接口&#xff09;在现代软件开发和数据处理中扮演着至关重要的角色。它不仅是数据交换的桥梁&#xff0c;更是不同系统、平台和应用程序之间的门户。深入理解API的功能、原理和应用&#xff0c;对于开发者、企业和数据科学家来说都是至关重要的。 A…

【python深度学习】——torch.einsum|torch.bmm

【python深度学习】——torch.einsum|torch.bmm 1. 基本用法与示例2. torch.bmm 1. 基本用法与示例 基本用法: torch.einsum(equation, *operands)equation: 一个字符串&#xff0c;定义了张量操作的模式。 使用逗号来分隔输入张量的索引&#xff0c;然后是一个箭头&#xff…

8259A芯片

目录 硬件结构 引脚图 引脚功能 内部结构 连接微处理器 芯片编程 相关硬件 工作流程 初始化命令字编程 操作命令字编程 相关硬件 工作方式 硬件结构 引脚图 多个芯片可通过接入IR7引脚实现级联。 引脚功能 INT引脚会接入CPU的INTR引脚(可屏蔽引脚) 1.处于非缓冲方式…

基于阿里云服务网格流量泳道的全链路流量管理(三):无侵入式的宽松模式泳道

作者&#xff1a;尹航 在前文《基于阿里云服务网格流量泳道的全链路流量管理&#xff08;一&#xff09;&#xff1a;严格模式流量泳道》、《基于阿里云服务网格流量泳道的全链路流量管理&#xff08;二&#xff09;&#xff1a;宽松模式流量泳道》中&#xff0c;我们介绍了流…

FastDFS分布式文件系统

一、概述 FastDFS是一款由国人余庆开发的轻量级开源分布式文件系统&#xff0c;它对文件进行管理&#xff0c;功能包括&#xff1a;文件存储、文件同步、文件访问&#xff08;文件上传、文件下载&#xff09;等&#xff0c;主要解决大容量文件存储和高并发访问问题&#xff0c…

佛教祭拜小程序-寺庙小程序-纪念馆小程序

大家好&#xff0c;我是程序员小孟。 现在有很多的产品或者工具都开始信息话了&#xff0c;寺庙或者佛教也需要小程序吗&#xff1f; 当然了&#xff01; 前面我们还开发了很多寺庙相关的小程序&#xff0c;都有相关的介绍&#xff1a; 1,优质的寺庙小程序-H5寺庙网页 今天…

文献解读-肿瘤测序-第五期|《局部晚期或转移性儿童及青少年分化型甲状腺癌的基因特征与临床特征及131I疗效的关系》

关键词&#xff1a;应用遗传流行病学&#xff1b;群体测序&#xff1b;肿瘤测序&#xff1b; 文献简介 标题&#xff08;英文&#xff09;&#xff1a;The relationship between genetic characteristics and clinical characteristics and the efficacy of 131I therapy in c…

Ktor库的高级用法:代理服务器与JSON处理

在现代网络编程中&#xff0c;Ktor是一个高性能且易于使用的框架&#xff0c;它提供了对异步编程、WebSockets、HTTP客户端和服务器等特性的原生支持。Ktor是使用Kotlin语言编写的&#xff0c;充分利用了Kotlin的协程特性来简化异步编程。本文将深入探讨Ktor库的高级用法&#…

Doris Connector 结合 Flink CDC 实现 MySQL 分库分表

1. 概述 在实际业务系统中为了解决单表数据量大带来的各种问题&#xff0c;我们通常采用分库分表的方式对库表进行拆分&#xff0c;以达到提高系统的吞吐量。 但是这样给后面数据分析带来了麻烦&#xff0c;这个时候我们通常试将业务数据库的分库分表同步到数据仓库时&#x…

如何让委外加工管理更轻松?

中小制造企业&#xff0c;受制于场地、资金、环保、质量、交期等等因素影响&#xff0c;在生产制造过程中&#xff0c;多数会将一些生产工序或者在制品外发给其他制造工厂进行委外加工生产。随着各地监管部门对环境、能耗管控力度的加大&#xff0c;这种情况在机加工行业尤为突…

【笔记】基于差分法的白噪声道路模型

理论如下: 式(13-7)中等式左边的路面位移随时间的导数可用差分法近似,即 整理可得 代码如下: C级路面 clc clear close all%% 参数定义 dt=0.01;%仿真间隔时间 t_end=10;%仿真总时长 t=0:dt:t_end; n00=0.011;%下截止频率 u=60;%车速,km/h u=u/3.6;%车速转化为m/s f0=2* …

SmartEDA:革新电子教学,引领未来工程师的启航之旅

在数字化浪潮席卷而来的今天&#xff0c;电子教学已成为教育领域的一股强劲风潮。SmartEDA&#xff0c;作为一款前沿的电子教学辅助工具&#xff0c;正以其独特的魅力&#xff0c;助力学校电子教学的蓬勃发展&#xff0c;打造未来工程师的摇篮。 SmartEDA凭借其智能化的特点&a…

运行软件缺失vcruntime140.dll怎么办?vcruntime140.dll缺失的详细解决方法分享

vcruntime140.dll 是一个动态链接库文件&#xff0c;它是 Microsoft Visual C Redistributable Package 的一部分&#xff0c;为使用 Visual C 编译器开发的应用程序提供必要的运行时环境。该文件包含了大量应用程序运行时需要调用的库函数&#xff0c;这些函数是实现 C 标准库…

1.计算机系统概述

1.计算机系统概述 1.1计算机系统层次结构 第1级是微程序机器层 第2级是传统机器语言层 第3级是操作系统层&#xff1a;向上提供广义指令 第4级是汇编语言层 第5级是高级语言层 1、2层是硬件&#xff0c;3-5层是软件。 没有配备软件的纯硬件系统称为裸机&#xff0c;第3-5…

AWVS+BP+XRAY三层联动扫描漏洞

1. 前言 本报告详细记录了使用AWVS&#xff08;Acunetix Web Vulnerability Scanner&#xff09;、Burp Suite和Xray进行的漏洞扫描结果。旨在帮助开发团队识别和修复系统中的安全漏洞&#xff0c;提升整体安全性。 2. 扫描工具简介 AWVS&#xff08;Acunetix Web Vulnerabi…

Spring家族中的消息通信解决方案

相信大家对消息通信架构以及各种消息中间件应该都不陌生。在分布式系统的设计和开发过程中&#xff0c;消息通信是用于实现系统解耦、提高扩展性的一大技术体系。而业界关于如何实现消息通信系统也有很多解决方案和对应的开发框架。不知道你有没有发现&#xff0c;在我们每天都…

小米路由器如何设置去广告功能,如何设置小米路由器的自定义Hosts(小米路由器如何去除小米广告、去除小米电视盒子开屏广告、视频广告)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 实现方案 📒📝 操作步骤📝 注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 小米设备的广告一直是用户头疼的问题,无论是开屏广告、应用内广告还是系统广告,都影响了用户体验。本文将详细介绍如何通过小米路由器实现去除广告…