前言
水龍頭是什么
水龍頭這個名字總會讓我想起生活中把水龍頭開關擰到無限接近但又不達到關閉狀態的大媽們,這樣可以讓水一滴一滴緩慢滴出,但又不會觸發水表計費,彰顯出她們豐富的生活經驗和生存智慧,
水龍頭是贈送小額位元幣的服務,
為了讓用戶可以快速嘗試Bitcoin SV網路,會有人搭建水龍頭服務,給用戶贈送給小額位元幣,這樣用戶就可以用這些幣嘗試使用Bitcoin SV網路,如:轉賬、測驗、寫入資料等,
——wiki.bsv.info
合約需求
本文介紹如何通過智能合約直接在鏈上提供水龍頭服務,該服務滿足如下需求:
- 任何人都可以向合約中充值,
- 每隔一段時間,任何人都可以從合約中提取一定額度的BSV,
完整的實作代碼放在了文末,
準備知識
閱讀本文前需要先了解OP_PUSH_TX的相關知識,推薦閱讀如下文章:
- 《深入學習位元幣腳本之 OP_PUSH_TX》系列
- 《BSV智能合約》系列
- 《打開sCrypt的盒子》系列
代碼分析
合約有兩個公有函式,代表著兩個合約功能:
- 充值
charge - 贈送(滴水)
drop
充值功能分析
函式引數
SigHashPreimage preImage:當前tx的簽名哈希原像,如果你不理解這個引數的含義,那么說明你沒有看準備知識部分推薦的文章,int chargeAmount:充值聰數,Ripemd160 changePKH:找零用的公鑰Hash,int changeAmount: 找零聰數,
引數檢查
require(Tx.checkPreimage(preImage));
require(chargeAmount > 0);
對引數進行取值范圍的檢查,
因為sCrypt目前還不支持unsigned int型別,所以需要檢查chargeAmount引數是正數,避免出現利用充值函式從合約中取走幣的漏洞,
構造合約輸出
合約規定充值tx最多會有兩個有先后順序的輸出:
- 充值后的合約
- 找零(可選)
bytes output0 = this.composeChargeOutput0(preImage, chargeAmount);
function composeChargeOutput0(SigHashPreimage preImage, int chargeAmount):bytes{
bytes lockingScript = Util.scriptCode(preImage);
int contractTotalAmount = Util.value(preImage) + chargeAmount;
return Util.buildOutput(lockingScript, contractTotalAmount);
}
充值前合約里的余額加上要充值的額度就是充值后的合約里的余額,這里你就可以理解為什么要檢查chargeAmount是正數了,
充值不會改變合約的腳本,所以用上一個合約的腳本和充值后的余額就可以拼出充值后的合約輸出,
構造找零輸出
bytes output1 = this.composeChargeOutput1(changePKH, changeAmount);
function composeChargeOutput1(Ripemd160 changePKH, int changeAmount):bytes{
bytes output1 = b'';
if(changeAmount > 546){
output1 = Util.buildOutput(Util.buildPublicKeyHashScript(changePKH), changeAmount);
}
return output1;
}
如果找零額度小于546聰,則認為不需要找零,也就沒有找零輸出,
根據找零PKH構造出P2PKH格式的輸出腳本,再結合找零額度,就可以構造出完整的輸出,
校驗所有輸出的哈希值
Sha256 hashOutputs = hash256(output0 + output1);
require(hashOutputs == Util.hashOutputs(preImage));
不解釋,
贈送功能分析
函式引數
SigHashPreimage preImage:當前tx的簽名哈希原像,Ripemd160 receiver:接收者的公鑰哈希,
引數檢查
require(Tx.checkPreimage(preImage));
//在nSequence < 0xFFFFFFFF時nLockTime才有效, https://wiki.bitcoinsv.io/index.php/NLocktime_and_nSequence
require(Util.nSequence(preImage) < 0xFFFFFFFF);
為了滿足兩次贈送轉賬之間的時間間隔,需要使用位元幣的nLocktime功能,設定了nLocktime的值后,能夠限制合約在該時間之前被花費,也就阻止了在該時間之前發起下一次贈送轉賬,
要讓nLocktime生效,則需要讓合約輸入的nSequence小于0xFFFFFFFF,所以需要對該值進行檢查,
手續費和贈送額度
int dropAmount = 2000000;
int fee = 3000;
我們簡單粗暴地把每次贈送的數額設定為0.02BSV,不能多也不能少,
每次轉賬費用設定為3000聰,基本上相當于0.5聰每位元組,
構造合約輸出
合約規定贈送tx最多會有兩個有先后順序的輸出:
- 合約輸出
- 贈送輸出
bytes output0 = this.composeDropOutput0(preImage, dropAmount, fee);
function composeDropOutput0(SigHashPreimage preImage, int dropAmount, int fee):bytes{
bytes prevLockingScript = Util.scriptCode(preImage);
int scriptLen = len(prevLockingScript);
int fiveMinutesInSecond = 300;
int newMatureTime = this.getPrevMatureTime(prevLockingScript, scriptLen) + fiveMinutesInSecond;
require(Util.nLocktime(preImage) == newMatureTime);
bytes codePart = this.getCodePart(prevLockingScript, scriptLen);
bytes script = codePart + pack(newMatureTime);
int amount = Util.value(preImage) - dropAmount - fee;
return Util.buildOutput(script, amount);
}
合約輸出的資料部分是一個四位元組的時間戳,也就是matureTime,該值與tx的nLocktime相等,表示合約在該時刻之后才可以被花費(充值或贈送),matureTime的目的是為了記錄合約所在的上一個tx的nLocktime,從而可以用該值對當前tx的nLocktime進行校驗,
合約設計成每隔5分鐘可以被花費一次,那么matureTime的值每次都會增加300秒,保證nLocktime的值也是按照該規律增加,從而最終保證每次花費合約之間的間隔為5分鐘,
老合約的余額減去贈送的數額,再減去手續費,就是新合約里的余額,腳本部分和余額部分組合在一起形成新合約的輸出,
構造贈送輸出
bytes output1 = this.composeDropOutput1(receiver, dropAmount);
function composeDropOutput1(Ripemd160 receiver, int dropAmount):bytes{
bytes script = Util.buildPublicKeyHashScript(receiver);
return Util.buildOutput(script, dropAmount);
}
不解釋,
檢查所有輸出的哈希值
Sha256 hashOutputs = hash256(output0 + output1);
require(hashOutputs == Util.hashOutputs(preImage));
不解釋,
總結
測驗網路上已經部署了該合約:
- 部署 tx
- 贈送tx
- 充值tx
感謝曉峰大爺提供測驗網路的BSV,
完整代碼
import "util.scrypt";
contract Faucet {
public function charge(SigHashPreimage preImage, int chargeAmount, Ripemd160 changePKH, int changeAmount) {
require(Tx.checkPreimage(preImage));
require(chargeAmount > 0);
bytes output0 = this.composeChargeOutput0(preImage, chargeAmount);
bytes output1 = this.composeChargeOutput1(changePKH, changeAmount);
Sha256 hashOutputs = hash256(output0 + output1);
require(hashOutputs == Util.hashOutputs(preImage));
}
public function drop(SigHashPreimage preImage, Ripemd160 receiver){
require(Tx.checkPreimage(preImage));
//在nSequence < 0xFFFFFFFF時nLockTime才有效, https://wiki.bitcoinsv.io/index.php/NLocktime_and_nSequence
require(Util.nSequence(preImage) < 0xFFFFFFFF);
int dropAmount = 2000000;
int fee = 3000;
bytes output0 = this.composeDropOutput0(preImage, dropAmount, fee);
bytes output1 = this.composeDropOutput1(receiver, dropAmount);
Sha256 hashOutputs = hash256(output0 + output1);
require(hashOutputs == Util.hashOutputs(preImage));
}
function composeChargeOutput0(SigHashPreimage preImage, int chargeAmount):bytes{
bytes lockingScript = Util.scriptCode(preImage);
int contractTotalAmount = Util.value(preImage) + chargeAmount;
return Util.buildOutput(lockingScript, contractTotalAmount);
}
function composeChargeOutput1(Ripemd160 changePKH, int changeAmount):bytes{
bytes output1 = b'';
if(changeAmount > 546){
output1 = Util.buildOutput(Util.buildPublicKeyHashScript(changePKH), changeAmount);
}
return output1;
}
function getPrevMatureTime(bytes lockingScript, int scriptLen):int {
return unpack(lockingScript[scriptLen - 4 :]);
}
function getCodePart(bytes lockingScript, int scriptLen):bytes{
return lockingScript[0:scriptLen - 4];
}
function composeDropOutput0(SigHashPreimage preImage, int dropAmount, int fee):bytes{
bytes prevLockingScript = Util.scriptCode(preImage);
int scriptLen = len(prevLockingScript);
int fiveMinutesInSecond = 300;
int newMatureTime = this.getPrevMatureTime(prevLockingScript, scriptLen) + fiveMinutesInSecond;
require(Util.nLocktime(preImage) == newMatureTime);
bytes codePart = this.getCodePart(prevLockingScript, scriptLen);
bytes script = codePart + pack(newMatureTime);
int amount = Util.value(preImage) - dropAmount - fee;
return Util.buildOutput(script, amount);
}
function composeDropOutput1(Ripemd160 receiver, int dropAmount):bytes{
bytes script = Util.buildPublicKeyHashScript(receiver);
return Util.buildOutput(script, dropAmount);
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/179078.html
標籤:AI
