去年我常驻在某个东南亚海岛做远程开发,团队分布在六个时区。公司考勤用的是打卡软件,每次定位打卡我都会截个图发到工作群,领导说"可以了",但我们都知道那张截图没有任何技术含金量,也经不起任何质疑。后来一次旅行险理赔把我彻底点醒了——我在海边骑摩托摔伤,保险公司核赔时直接拒了:没有医院现场记录,没有目击证人签名,我的文字描述和手机相册照片都被算作"单方证据",不构成"在场证明"。从那一刻起,我开始认真琢磨一个问题:数字游民能不能像区块链上的交易一样,拿出一个任何人无法篡改、无法抵赖的"在场凭证"?
两个月后,我把这个想法写成一份技术白皮书,标题就叫《数字游民的区块链:关于在场证明的技术白皮书》。这篇东西不是行业里常见的概念营销文档,而是一套完整的技术设计方案:如何把"某个时间、某个地点、某个人真实出现在那里"这个事实,变成链上可验证的数字凭证。现在把白皮书的整体思路、架构设计、合约代码和踩坑记录整理出来,希望能给正在做远程考勤、保险科技、线下活动凭证、DAO贡献证明的朋友一些参考。
1. 内容整体设计与思路拆解
1.1 白皮书的定位:不是链上定位,而是链上存证
很多朋友看到"数字游民+区块链"第一反应是"用区块链做一套定位系统",这个理解从一开始就是错的。GPS定位是手机传感器的事,区块链根本管不了,也不需要管。真正要解决的新问题,是把"定位结果"变成"证据"。
举个例子。两个人在同一个聊天群里,A发了一张自己在北京地标前打卡的照片,B说"这是P的吧"。群里所有人围观,但谁也证明不了照片真伪。如果A发的不是照片,而是一笔写入区块链的记录,记录上写着"14:30,坐标某个编码范围,签名人A"——疑问就从"这张图是不是P的"变成了"这条链上记录是不是真的存在"。后者是任意第三方都可以直接验证的。这就是在场证明和普通打卡截图最本质的区别:它把信任对象从"我"变成了"公开账本"。
所以整个白皮书的设计思路是一条直线:把手机的定位采集结果,经过防伪校验、标准化编码、签名之后,写入公开分布式账本。任何一个验证方不需要信任你,不需要信任签发机构,只需要查一条链上记录,就能确认"某人在某时某刻出现在某地附近"。
1.2 需求拆解:三个角色与三张场景表
在设计系统之前,我先拆了角色,整个业务里一共只有三类人:
- 证明人:数字游民本人,带着手机,愿意配合签到;
- 验证方:公司HR、保险公司核赔员、活动主办方、社区DAO成员;
- 公证层:区块链网络,负责让记录不可篡改、公开可查。
再列出场景。我整理了三个最真实、最不容易被质疑的落地场景,做了一个对比表:
| 场景 | 验证方要什么 | 现在的做法 | 白皮书方案 |
|---|---|---|---|
| 远程考勤 | 确认员工在约定时间段在岗 | 打卡软件截图、GPS定位 | 每日在场凭证+按周自动聚合 |
| 保险理赔 | 确认事故发生在特定地点区域 | 医院证明、目击证言 | 事故时间窗口内的链上在场记录 |
| 线下活动凭证 | 确认真人到场,防羊毛党 | 扫码核销 | 现场签名上链,生成不可转让凭证 |
个人经验是:远程考勤和保险理赔是需求最刚、付费意愿最强的场景,做产品优先从这两个切口进。DAO活动凭证虽然概念上很酷,但绝大多数DAO的体量撑不起单独一条业务线,更适合作为MVP阶段的测试场景,用来验证技术闭环。
1.3 为什么是区块链:信任第三方缺失的必然选择
有人会问,普通数据库加数字签名不也能实现吗?理论上行,但实践中有三个死穴。第一,数据库在签发方手里,签发方可以删改、可以抵赖;第二,验证方不信任签发方,还要额外建立一层信任链路;第三,数据库一旦被入侵,所有记录全军覆没,连"有没有被改过"都查不出来。
区块链在这三个问题上都有结构性优势。但这里必须诚实一点:区块链解决的是"记录之后不被篡改、不被抵赖",解决不了"采集那一刻输入是否真实"。手机拿到的垃圾定位数据,上链之后依然是垃圾。所以这套方案里最重要的设计不在合约层,而在采集端的防伪校验层。认清这个边界,后面所有设计都不会跑偏。
2. 核心细节解析与实操要点
2.1 "在场"的数字化模型:身份+时间+位置
一个合格的在场证明,本质是一个三元组:谁(身份)、什么时间(时间)、什么地方(位置)。三个维度都必须是机器可读的,缺任何一个都立不住。
身份维度,我用的方案是钱包地址,后续可扩展成DID。钱包地址的好处是天然支持签名,不需要额外做KYC;坏处是普通用户手里不一定有钱包,需要做一层托管或引导。时间维度,统一用Unix时间戳,再切成"时间桶"。在场证明不需要精确到秒,切成5分钟一个桶,既方便批量聚合,又能防止高频刷单。位置维度,不用经纬度,用Geohash字符串。Geohash的优势在于把二维坐标编码成一维字符串,同一区域的编码前缀相同,验证时可以通过前缀长度灵活控制精度。
举例说明,某个定位点编码成"drp8fg",这个6位字符串的覆盖范围大约是1.2公里乘0.6公里;如果加长到第7位变成"drp8fgp",就会锁定到大约153米乘153米的一个格子。数字游民的在场认定,7位精度刚刚好——既保护隐私,又不会因为室内GPS抖动导致编码一跳就跳完。
2.2 防伪设计:四类信源交叉验证
手机定位的采集结果天然不防伪,刷机、模拟器、注入虚拟定位Provider,二十分钟就能破解。所以在采集层我做了四类信源交叉验证,这是整个系统里最核心的防伪机制:
| 信源 | 权重 | 通过标准 | 备注 |
|---|---|---|---|
| GPS | 40 | 卫星数≥6,水平精度<25m | 最容易伪造,只能作为参考 |
| WiFi指纹 | 25 | 扫描到的AP集合与历史指纹相似度≥80% | 室内效果远好于GPS |
| BLE信标 | 20 | RSSI>-70dBm,且与信标注册表匹配 | 需要线下提前部署锚点 |
| 基站ID | 15 | 相邻两次扫描Cell ID一致 | 用于粗筛异常跳变 |
四个信源加权求和,得到0到100的可信度分数。低于80分直接不签发在场凭证;分数在80到100之间,按分数展示"可信度",并为后续审计留底。这套方案实测效果不错:模拟器伪造的定位基本被挡在70分以下,主要原因是伪造者很难同时把WiFi指纹和基站ID一起改了,单靠GPS造假在交叉验证面前很容易露馅。
需要注意一点,BLE信标需要物理部署,在共享办公空间、音乐节闸口这类小范围商业场景效果最好;纯野外场景只能依赖GPS加基站,防伪能力会下降一档。所以整套方案不是"区块链让定位变可信",而是"把定位可信度的评估标准化、可审计"。
2.3 隐私保护:不暴露精确位置的在场证明
银行对账单能证明你的消费,但没人希望账单被公开在广场上。链上在场记录是同样的道理。MVP阶段我用三层保护来解决隐私焦虑。
第一层,位置模糊。默认只存7位Geohash,多余精度在本地直接丢弃,链上永远拿不到精确经纬度。第二层,哈希承诺。验证方要证明"某人出现在某个范围"时,证明人可以在本地计算一个承诺值,也就是对Geohash加随机数做哈希,链上只存承诺;需要验证时再公开原始Geohash和随机数。第三层,身份可选解耦。场所在场记录不直接绑定钱包,而是绑定一次性会话令牌,用户事后自行决定要不要把令牌与身份关联。
零知识证明是最优解,但开发成本高,MVP阶段不建议碰。如果场景真的涉及金融级隐私要求,后续可以引入zk-SNARKs,但对大多数MVP来说,Geohash加哈希承诺已经够用。
3. 架构设计与核心环节实现
3.1 五层架构设计
整个系统拆成五层后,每一层的职责都很单一,替换成本也低:
| 层级 | 职责 | 关键技术 | 对应模块 |
|---|---|---|---|
| 采集层 | 多源信号采集 | GPS/WiFi/BLE/基站 | 手机小程序 |
| 验证层 | 可信度评估、防伪过滤 | 加权评分、设备指纹 | 边缘服务/后端 |
| 聚合层 | 批量打包、时间桶切分 | 队列、Merkle树 | Node.js服务 |
| 上链层 | 数据上链、凭证签发 | 智能合约 | 公链测试网 |
| 应用层 | 展示、验证、API | QR Code、REST API | 管理后台/小程序 |
这五层的划分逻辑其实很直白:采集层负责"感知",验证层负责"判断",聚合层负责"省钱",上链层负责"公正",应用层负责"给人用"。每一层都可以独立替换,比如采集层今天用手机GPS,明天换成车载定位终端,对上层的合约完全无感。这也是我坚持把合约做薄的原因——合约越简单,上层业务重构时越不需要动链上逻辑。
3.2 一条在场证明从生成到验证的完整流程
我把白皮书里的流程整理成一个端到端的时序,方便你对照自己的实现:
- 用户在小程序点击"签到",进入采集流程。
- 手机在3秒内采集GPS、WiFi扫描结果、BLE扫描结果、基站信息,打包成一条JSON发给后端。
- 后端先做三个粗筛:设备指纹是否在黑名单、当前时间窗口内是否已有在场记录、四类信源是否齐全。全部通过后进入评分模块。
- 评分结果达到80分以上,后端用私钥签发一条"待上链记录",同时生成短期二维码展示给用户。
- 聚合模块每5分钟把积压的待上链记录做成一个批次,调用合约的batchStore函数。
- 合约对每条记录计算事件ID并存储,返回链上确认信息。
- 验证方扫描二维码,或通过API传入事件ID,后端从链上读取记录,比对身份、区域和时间桶,输出验证结果。
第4步和第5步的配合是整个体验的关键。实时输出二维码给用户,是为了解决"区块链确认慢"的体验痛点;批量延迟上链,是为了解决"每次签到都上链成本太高"的经济痛点。在信任语义上,上链前用户拿到的是"签发方背书",上链后拿到的是"网络共识背书",两层背书叠加,验证力是逐步增强的。
3.3 合约设计与核心代码
MVP阶段的合约只做两件事:存记录、查记录。越简单越好,不要在这一层堆业务逻辑。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract PresenceLog { address public operator; // eventId => 上链区块时间 mapping(bytes32 => uint256) public records; event PresenceStored( bytes32 indexed eventId, address indexed subject, bytes32 geohash, uint256 timeBucket, uint8 confidence ); modifier onlyOperator() { require(msg.sender == operator, "PresenceLog: not operator"); _; } constructor(address _operator) { operator = _operator; } function batchStore( address[] calldata subjects, bytes32[] calldata geohashes, uint256[] calldata timeBuckets, uint8[] calldata confidences ) external onlyOperator returns (bytes32[] memory eventIds) { require( subjects.length == geohashes.length && geohashes.length == timeBuckets.length && timeBuckets.length == confidences.length, "PresenceLog: length mismatch" ); eventIds = new bytes32[](subjects.length); for (uint256 i = 0; i < subjects.length; i++) { bytes32 eventId = keccak256( abi.encodePacked( subjects[i], geohashes[i], timeBuckets[i], block.number ) ); require(records[eventId] == 0, "PresenceLog: duplicate"); records[eventId] = block.timestamp; eventIds[i] = eventId; emit PresenceStored( eventId, subjects[i], geohashes[i], timeBuckets[i], confidences[i] ); } } }几个设计决策值得单独说明。
第一,只允许operator调用batchStore,普通用户不直接调合约。原因是gas费对移动端用户不友好,而且批量打包能把一条记录的成本摊到很低。operator账户由服务端管理,私钥放在密钥管理服务里,不要让私钥出现在开发机明文目录。第二,geohash用bytes32存而不是string,因为Solidity里string的存储开销更大,而Geohash本质上是一个Base32整数,用bytes32既省gas又方便比较相等性。第三,事件ID里混入block.number,是为了防止用户提前预知自己的事件ID,也避免同一个人在同一个时间桶被重复刷记录。第四,记录时间用block.timestamp,虽然验证者可以在小范围内调整这个值,但对"分钟级在场证明"来说精度绰绰有余;如果要拿这套方案做秒级时序判断,就不合适了。
4. 实操过程与关键参数标定
4.1 环境准备
跑MVP我用了某条兼容虚拟机架构的公链测试网,不需要真实资产,测试币免费领,确认速度也够快。本地调试阶段还可以先起一个本地测试网络,等逻辑稳定后再切远程测试网。
需要准备的东西不复杂:Node.js环境、一个合约开发框架、一个支持自定义RPC的钱包、一份测试网的RPC配置。部署命令就两行:
npx hardhat compile npx hardhat run scripts/deploy.js --network testnet部署脚本里有个细节:constructor参数要传入operator地址,建议单独建一个冷钱包做operator,不要用开发机默认账户。这个冷钱包的私钥一旦泄露,整个在场记录系统的签发端就失控了。
4.2 三个必须标定的关键参数
在真正跑通业务之前,有3个参数必须实测标定,否则上线必踩坑。
Geohash精度。我在测试区域收集了100个真实定位点,分别计算7位和8位Geohash,统计相邻两点落在同一格子的比率。实测下来7位在城区的稳定度大约90%,8位只有75%。原因很简单,8位格子覆盖范围小,GPS一抖动就容易跳格子。所以默认选7位,对大多数场景足够,保险场景可以接受一定程度的误报。
可信度阈值。我用一台正常手机、一台模拟器、一台刷过机的手机同时跑了200次评分,模拟器平均55分,刷机版65分,正常手机90分。阈值设在80分时,模拟器全拒,刷机版偶尔放过。这个比例可以接受,因为雷达图上刷机版被放过的场景往往发生在户外GPS信号很好的地方,而户外场景里WiFi和基站交叉验证能补上防御。
时间桶长度。5分钟是一个经验平衡点。切1分钟会在网络拥堵时大量超时,切30分钟又无法区分"上午10:15到场"还是"10:40到场"。5分钟正好匹配大多数考勤和理赔场景的时间粒度。
这些数值都是我在自己环境里实测得到的,不同城市、不同机型的表现会不一样。关键不是照抄这几个数,而是方法:任何参数都不能拍脑袋,必须在目标场景里做小样本统计后再定。
4.3 端到端实测记录
我找了两个朋友做了一次模拟验证实验。A同学用一台正常手机,在14:00走进某共享办公空间触发签到;B同学在同一时间用模拟器伪造了自己在另一个城市的位置。
A同学的链路走完是:采集、评分92分、签发、批量上链、生成事件ID,同步展示在二维码页面。验证方扫码后看到"在场记录真实,时间桶14:00-14:05,区域有效",可信度92。B同学的链路走到评分就断了:评分49分,后端直接拒绝签发,App端显示"当前环境信号不可信,无法完成在场登记"。
这个结果不算惊艳,但它说明整套闭环是站得住的。B如果想更真实地伪造,他需要同步伪造WiFi指纹、基站ID和设备指纹,攻击成本远高于PS一张截图。这里有个产品化细节特别提醒:二维码页面必须同时展示"临时凭证签发时间"和"链上确认时间",用户看到"链上确认时间"出现,才算真正完成上链。我在测试时经常发现用户以为二维码生成就是上链完成,这种引导问题在正式产品里很影响信任。
5. 常见问题与排查技巧实录
5.1 室内GPS漂移到隔壁楼
室内GPS信号会被建筑反射,定位点经常飘出几十米。我踩过最夸张的一次:人在一楼大厅,GPS显示在200米外的一条马路上,Geohash直接跳了两格,验证失败。
对策是Geohash做前缀模糊匹配。存储时用7位,验证时允许比较前6位。前6位对应约1.2公里范围,对"人是否在这栋楼、这个园区"的校验来说已经足够。更优的方案是室内场景把WiFi指纹作为主要信源,GPS只做辅助,因为WiFi的定位稳定性在室内远好于GPS。
5.2 测试网拥堵导致上链超时
在场凭证有天然的时效性:用户14:00签到,如果到14:40才被确认,这个凭证的说服力就弱了。测试网偶尔也会拥堵,我在实验里遇到过gas费突然飙升、batchStore等了10分钟没上链的情况。
对策分两层。实时层,先把"签发方背书的临时凭证"通过二维码给到用户,临时凭证有效期60分钟;异步层,批量上链任务带重试队列,失败自动回滚并从队列重新提交。产品文案上必须区分"二维码已生成"和"链上已确认"两个状态。这套机制上线之后,再没出现过因网络拥堵丢单的情况。
5.3 测试币水龙头不可用
测试网水龙头有时不稳定,等半天不到账,合约都没法部署。我的经验是备三个不同水龙头源,主源挂了立刻换备源;也可以在本地把业务逻辑全部调通,最后再切到远程测试网做集成验证。部署完成后,合约地址和ABI文件要留好,不要随手丢在临时目录,后面很多脚本都要引用。
5.4 隐私质疑:链上记录永久可见
测试时被一个朋友问住:"你在链上存了我每个工作日10点钟的位置,三年后谁都能看到我来没来过这个城市,这算不算监控?"这个问题问得很准。
我的处理是:MVP阶段默认不存精确地址,只存7位Geohash,覆盖范围约三万平方米,能证明"在这个区域",但无法还原到具体门牌。同时提供"解绑功能",用户可以在后端把自己的历史记录从身份关联中摘除,链上数据虽然删不掉,但不再与任何可识别的身份对应。产品侧必须把这两点写进隐私说明,否则很难过合规审查。
5.5 设备指纹误杀正常用户
刚开始做防伪时,设备指纹模块设计得太激进,导致一台手机升级系统后指纹变化被判定为新设备,连续签到断档。排查了很久才发现是设备指纹硬校验的问题。
经验是:设备指纹不能做硬校验,只能做软评分。密钥层面用密码学签名保证,设备指纹只用来识别"可疑变化",发现变化时触发二次验证,比如短信验证码,而不是直接拒绝。防伪系统做太严会伤正常用户体验,做太松又挡不住攻击,这套平衡只能靠真实用户反馈来调。
6. 影响范围与后续延展
6.1 对现有场景的改造空间
在场证明这个能力其实远不止数字游民能用。想几个例子:共享办公的闸机记录原本是某一家运营商的私有数据,换成链上在场记录后,同一张凭证可以在全国连锁空间互认;无人仓库的巡检机器人每次到场打卡,审计方不用再信任仓储企业的后台数据;宠物寄养、家政上门这类服务,服务人员到场后自动生成凭证,平台处理纠纷时多一份客观证据;文旅打卡也从"拍个照"升级成"生成链上纪念凭证",本身就能变成用户增长玩法。
这些场景背后的技术完全一致:身份、时间、位置、签名、上链,区别只在于采集设备是手机还是巡检机器人,以及可信度评分模型怎么调。所以我把底层设计成通用模块,不绑定"数字游民打卡"这一个场景。谁需要"谁在什么时间出现在哪里"的可验证答案,谁就是这套系统的客户。
6.2 技术演进的三个方向
第一个方向是DID与可验证凭证融合。钱包地址最大的问题是普通用户没有,后续可以做DID服务,让用户用邮箱或手机号也能建立在场身份,链上只保留DID文档。第二个方向是Merkle聚合证明。一个月甚至一年的在场记录,可以先生成一颗Merkle树,链上只存根哈希,验证某一条记录时给出Merkle证明路径。这样链上存储成本几乎为零,代价是增加一点开发复杂度。第三个方向是硬件锚点。GPS和WiFi都能被软件伪造,但如果场地内放置固定NFC锚点,用户必须碰一下才能签到,攻击成本就完全不一样了。共享办公和活动闸机是最容易部署这类硬件的场景。
6.3 一些实在的提醒
最后说点不中听但重要的话。这套系统的技术门槛并不高,真正难的是"让第三方认可"。公司HR会问:这个凭证长什么样?法院会问:这算不算电子证据?保险公司的核赔系统会问:你的可信度评分标准是什么?这些问题的答案都不在代码里,而在业务合作和持续运营里。
所以如果你打算做类似项目,我的建议是:先找一个小而具体的场景,找一家愿意陪你测试的机构,用几个月时间把"凭证被认可"这个环节走通,再考虑规模化。合约和采集端的代码真的不是瓶颈,业务侧的信任建立才是。
写这份白皮书加上自己动手跑通全流程,我最大的收获其实是四个字:边界意识。区块链不是魔法,它没法让你物理上真的到达现场,也没法让所有人无条件信任你手机里的传感器。它能做的只是把"采集、评估、存证、验证"这条链条变成公开可审计的流程,让每一个环节都可以被第三方独立复核。数字游民需要的,从来不是什么花哨的技术,只是一个"谁都改不了的在场记录"。
自己在搭建过程中踩过的坑,比如GPS漂移、测试网拥堵、隐私质疑,回头看都是好事,它们逼着我把方案从"能跑的Demo",逐步打磨成"经得起追问的工程方案"。如果你也在做类似的尝试,建议从这份思路出发,先从一个小场景做起,先把一条记录完整跑通,再考虑规模化。技术部分照搬问题不大,真正需要你花心思的,是找到那个愿意信任这套机制的第三方。