标准合约¶
多个合约¶
一个文件可以定义多个合约。在这种情况下,最后一个合约作为主合约并且被编译。其他合约是依赖项。
在下面这个例子中,标准的P2PKH合约被改写为两个其他合约:一个用来检查公钥和公钥哈希是否匹配的哈希谜题(hash puzzle)合约,还有一个检查签名和公钥是否匹配的Pay-to-PubKey(P2PK)合约。
contract HashPuzzle {
Ripemd160 hash;
public function spend(bytes preimage) {
require(hash160(preimage) == this.hash);
}
}
contract Pay2PubKey {
PubKey pubKey;
public function spend(Sig sig) {
require(checkSig(sig, this.pubKey));
}
}
contract Pay2PubKeyHash {
Ripemd160 pubKeyHash;
public function spend(Sig sig, PubKey pubKey) {
HashPuzzle hp = new HashPuzzle(this.pubKeyHash);
require(hp.spend(pubKey));
Pay2PubKey p2pk = new Pay2PubKey(pubKey);
require(p2pk.spend(sig));
}
}
导入(import)¶
或者,可以将上述合约分到三个文件中。 Pay2PubKeyHash
合约 import
其他两个合约作为依赖。这就可以重用其他人写的合约,成为构建合约库的基础。
合约可以通过 new
来实例化。require
函数的参数是条件表达式, 调用合约的 public
函数可以作为条件表达式传入。
import "./hashPuzzle.scrypt";
import "./p2pk.scrypt";
contract Pay2PubKeyHash {
Ripemd160 pubKeyHash;
public function spend(Sig sig, PubKey pubKey) {
HashPuzzle hp = new HashPuzzle(this.pubKeyHash);
require(hp.spend(pubKey));
Pay2PubKey p2pk = new Pay2PubKey(pubKey);
require(p2pk.spend(sig));
}
}
Library 库¶
库与合约相同,只是它不包含任何公共函数。它仅用于由合约或其他库导入。因此它不能被独立部署和调用。它经常用于对相关常量和静态函数进行分组。
library Util {
// number of bytes to denote some numeric value
static const int DataLen = 1;
// number of bytes to denote length serialized state, including varint prefix (1 byte) + length (2 bytes), change length to 4 when you need PushData4
static const int StateLen = 3;
// convert signed integer `n` to unsigned integer of `l` bytes, in little endian
static function toLEUnsigned(int n, int l): bytes {
// one extra byte to accommodate possible negative sign byte
bytes m = num2bin(n, l + 1);
// remove sign byte
return m[0 : len(m) - 1];
}
}
标准合约¶
sCrypt自带标准库,里面定义了许多常用的合约。标准库是默认就导入的,不需要写 import
语句。
Utils
库¶
Utils
库提供了一组常用的实用函数,例如函数 Utils.fromLEUnsigned
将有符号整数 n
转换为小端字节序的无符号整数。函数 buildOutput(bytes outputScript, int outputSatoshis) : bytes
从其脚本和 satoshi 数量构建 一个 tx 输出。
以下示例显示了如何在 RabinSignature
合约的中使用标准库的 Utils
库。
library RabinSignature {
static function checkSig(bytes msg, RabinSig sig, RabinPubKey pubKey) : bool {
int h = Utils.fromLEUnsigned(hash(msg + sig.padding));
return (sig.s * sig.s) % pubKey == h % pubKey;
}
static function hash(bytes x) : bytes {
// expand into 512 bit hash
bytes hx = sha256(x);
int idx = len(hx) / 2;
return sha256(hx[: idx]) + sha256(hx[idx :]);
}
}
Tx
库¶
对比特币脚本的一个严重误解是,脚本只能访问锁定脚本以及对应的解锁脚本中提供的数据。因此,脚本的范围和能力被大大低估了。
sCrypt提供了一个强大的合约叫做 Tx
。 它允许合约访问合约所在的 整个交易 ,包括锁定脚本和解锁脚本。我们把这种方法当成一个伪操作码 OP_PUSH_TX
,它可以把当前交易压到栈里,这样就可以在运行时访问了。更准确地说,可以访问的是在签名校验时用到的原像 preimage ,在 BIP143. 中有原像的详细定义。原像的数据格式如下:
- nVersion of the transaction (4-byte little endian)
- hashPrevouts (输入的输出点哈希 32字节哈希值)
- hashSequence (序列号哈希 32字节哈希值)
- outpoint (此输入的输出点 32字节哈希值 + 4字节小端)
- scriptCode of the input (输入对应的UTXO的锁定脚本)
- value of the output spent by this input (此输入对应的输出中包含的聪数 8字节小端)
- nSequence of the input (此输入的序列号 4字节小端)
- hashOutputs (输出的哈希 32字节哈希值)
- nLocktime of the transaction (交易的nLocktime 4字节小端)
- sighash type of the signature (签名类型 4字节小端)
例如,合约 CheckLockTimeVerify
确保合约中的币是时间锁定的,并且不能在时间达到 matureTime
之前花费,类似于 OP_CLTV。
contract CheckLockTimeVerify {
int matureTime;
public function spend(SigHashPreimage txPreimage) {
// using Tx.checkPreimage() to verify txPreimage
require(Tx.checkPreimage(txPreimage));
require(SigHash.nLocktime(txPreimage) >= this.matureTime);
}
}
更多细节可以在这篇文章 OP_PUSH_TX 技术 中找到。要自定义 ECDSA 签名,例如选择 sighash 类型,有一个名为 Tx.checkPreimageSigHashType()
的版本,支持自定义 sighash 类型。要自定义临时密钥,有一个更通用的版本,称为 Tx.checkPreimageAdvanced()
。请参阅 高级 OP_PUSH_TX 技术。
原像的 ScriptCode 通常包含整个锁定脚本。唯一的例外是当其中有 OP_CODESEPARATOR (OCS) 时。在这种情况下,scriptCode 是锁定脚本,但在执行 OP_CHECKSIG 之前删除包括最后执行的 OCS 之前的所有内容。

Tx
库提供了一组 OCS 版本方法来检查这个不包含完整锁定脚本的原像。在许多情况下,使用 OP_PUSH_TX 时不需要 scriptCode 或只需其一部分。 OCS 可以用来削减它的大小。例如,在下面的合约中,只需要整个原像的 nLocktime。我们使用 Tx.checkPreimageOCS()
,传统的 Tx.checkPreimage()
的变体。唯一的区别是 OCS 是在前者的 OP_CHECKSIG 之前插入的。另请注意,我们将 Tx.checkPreimageOCS()
作为最后一条语句以达到最大优化效果。
contract CheckLockTimeVerifyOCS {
int matureTime;
public function unlock(SigHashPreimage preimage) {
require(SigHash.nLocktime(preimage) > this.matureTime);
require(Tx.checkPreimageOCS(preimage));
}
}
SigHash
库¶
sCrypt 还提供了一个 SigHash
库来访问原像中的各个字段。例如,我们通常使用 SigHash.scriptCode
访问原像的 scriptCode
字段,使用 SigHash.value
访问原像的 value 字段,即在此合约中花费的比特币数量。
contract Clone {
public function unlock(SigHashPreimage txPreimage) {
require(Tx.checkPreimage(txPreimage));
bytes scriptCode = SigHash.scriptCode(txPreimage);
int satoshis = SigHash.value(txPreimage);
bytes output = Utils.buildOutput(scriptCode, satoshis);
require(hash256(output) == SigHash.hashOutputs(txPreimage));
}
}
HashedMap
库¶
HashedMap 库提供了一种类似于哈希表的数据结构。唯一键和它们对应的值在存储之前被散列。 HashedMap 的大多数函数不仅需要一个键,还需要它的索引,按键的哈希升序排列。
构造函数
HashedMap(bytes data)
使用一些初始数据创建一个HashedMap
实例。HashedMap<bytes, int> map = new HashedMap<bytes, int>(b''); // key and value types can be omitted HashedMap<int, bool> map1 = new HashedMap(b''); // key and value types cannot be omitted since they cannot be inferred auto map2 = new HashedMap<int, int>(b'');
SortedItem
SortedItem <T>
是一个通用结构体,它包含一个类型为 T 的 item 及 一个键索引 idx。
struct SortedItem<T> { T item; int idx; }
For most functions of HashedMap, a parameter named keyWithIdx of this type is required. It means that the key and its corresponding keyIndex should always be provided together.
实例方法
set(SortedItem <K> keyWithIdx, V val) : bool
使用 keyIndex 给定的键索引插入或更新 (key, val) 对。如果成功,则返回 true ;否则返回 false 。require(map.set({b'1234', 0}, 1)); // insert require(map.set({b'1234', 0}, 2)); // update it
canGet(SortedItem <K> keyWithIdx, V val): bool
检查是否存在 (key, val) 对, keyIndex 是键索引。如果成功,则返回 true ;否则返回 false 。require(map.canGet({b'1234', 0}, 2));
has(SortedItem <K> keyWithIdx) : bool
检查map中是否存在 key,其键索引为 keyIndex。如果两个条件都满足,则返回 true ;否则返回 false。require(map.has({b'1234', 0}));
delete(SortedItem <K> keyWithIdx) : bool
删除给定 key 的条目,键索引是 keyIndex。如果成功,则返回 true;否则返回 false 。require(map.delete({b'1234', 0}));
clear() : bool
删除map的所有条目。map.clear();
size() : int
返回 HashedMap 的大小,比如它包含的键的数量。int s = map.size();
data() : bytes
返回 HashedMap 的序列化数据表示。bytes b = map.data(); // this creates a deep copy of the map HashedMap<int, bool> mapCopy = new HashedMap(b);
HashedSet
库¶
HashedSet 库提供了一个类似集合的数据结构。它可以看作是一个特殊的 HashedMap,其中一个值与其键相同,因此被省略。唯一值在存储之前经过哈希处理。 HashedSet 的大多数函数都需要一个索引,按值的 sha256 哈希值升序排列。与 HashedMap 类似,这些函数也使用 SortedItem 类型参数。
构造函数
HashedSet(bytes data)
使用初始数据 data 创建一个HashedSet
实例。struct ST { int x; bool y; } HashedSet<ST> set = new HashedSet<ST>(b''); // key and value types can be omitted HashedSet<ST> set1 = new HashedSet(b''); // key and value types cannot be omitted since they cannot be inferred auto set2 = new HashedSet<ST>(b'');
实例方法
add(SortedItem <E> entryWithIdx) : bool
添加 entry 以使用 index 给出的键索引进行设置。如果成功,则返回 true;否则返回 false 。require(set.add({b'1234', 0}));
has(SortedItem <E> entryWithIdx) : bool 检查集合中是否存在 entry 条目,其键索引为 index 。如果两个条件都满足,则返回 true;否则返回 false 。
require(set.has({b'1234', 0}));
delete(SortedItem <E> entryWithIdx) : bool
删除给定 entry 条目,键索引为 index 。如果成功,则返回 true ;否则返回 false 。require(set.delete({b'1234', 0}));
clear() : bool
删除集合的所有条目。set.clear();
size() : int
返回集合的大小,即它包含的条目数。int s = set.size();
data() : bytes
返回集合的内部序列化数据。bytes b = set.data(); // this creates a deep copy of the set HashedSet<ST> setCopy = new HashedSet(b);
Constants
库¶
sCrypt 在 Constants
库中定义了一些常用的常量值。你可以在代码中的任何位置使用这些常量。
library Constants { // number of bytes to denote input sequence static const int InputSeqLen = 4; // number of bytes to denote output value static const int OutputValueLen = 8; // number of bytes to denote a public key (compressed) static const int PubKeyLen = 33; // number of bytes to denote a public key hash static const int PubKeyHashLen = 20; // number of bytes to denote a tx id static const int TxIdLen = 32; // number of bytes to denote a outpoint static const int OutpointLen = 36; }
完整列表¶
合约 | 构造函数参数 | 公共函数 |
---|---|---|
Utils | None | toLEUnsigned(int n, int l) : bytes
fromLEUnsigned(bytes b) : int
readVarint(bytes b) : bytes
writeVarint(bytes b) : bytes
buildOutput(bytes outputScript, int outputSatoshis) : bytes
buildPublicKeyHashScript(PubKeyHash pubKeyHash) : bytes
buildOpreturnScript(bytes data) : bytes
isFirstCall(SigHashPreimage preimage) : bool // return whether is the first call of series public function calls in stateful contract
|
Tx | None | checkPreimage(SigHashPreimage preimage) : bool
checkPreimageOpt(SigHashPreimage rawTx) : bool
checkPreimageOpt_(SigHashPreimage rawTx) : bool // set sigHashType in ASM
checkPreimageSigHashType(SigHashPreimage txPreimage, SigHashType sigHashType) : bool
checkPreimageAdvanced(SigHashPreimage rawTx, PrivKey privKey, PubKey pubKey, int inverseK, int r, bytes rBigEndian, SigHashType sigHashType) : bool
checkPreimageOCS(SigHashPreimage preimage) : bool
checkPreimageOptOCS(SigHashPreimage rawTx) : bool
checkPreimageOptOCS_(SigHashPreimage rawTx) : bool // set sigHashType in ASM
checkPreimageSigHashTypeOCS(SigHashPreimage txPreimage, SigHashType sigHashType) : bool
checkPreimageAdvancedOCS(SigHashPreimage rawTx, PrivKey privKey, PubKey pubKey, int inverseK, int r, bytes rBigEndian, SigHashType sigHashType) : bool
|
SigHash | None | nVersion(SigHashPreimage preimage) : bytes
hashPrevouts(SigHashPreimage preimage) : bytes
hashSequence(SigHashPreimage preimage) : bytes
outpoint(SigHashPreimage preimage) : bytes
scriptCode(SigHashPreimage preimage) : bytes
valueRaw(SigHashPreimage preimage) : bytes
value(SigHashPreimage preimage) : int
nSequenceRaw(SigHashPreimage preimage) : bytes
nSequence(SigHashPreimage preimage) : int
hashOutputs(SigHashPreimage preimage) : bytes
nLocktimeRaw(SigHashPreimage preimage) : bytes
nLocktime(SigHashPreimage preimage) : int
sigHashType(SigHashPreimage preimage) : SigHashType
|
HashedMap<K, V> | bytes data | set(SortedItem<K> keyWithIdx, V val) : bool
canGet(SortedItem<K> keyWithIdx, V val) : bool
delete(SortedItem<K> keyWithIdx) : bool
has(SortedItem<K> keyWithIdx) : bool
clear() : bool
size() : int
data() : bytes
|
HashedSet<V> | bytes data | add(SortedItem<V> entryWithIdx) : bool
delete(SortedItem<V> entryWithIdx) : bool
has(SortedItem<V> entryWithIdx) : bool
clear() : bool
size() : int
data() : bytes
|