改良版本
import "util.scrypt";
/**
* A non-fungible token enforced by miners at layer 1
*/
/*
Copyright
1. OP_PUSH_TX是nChain的研究员Ying Chan主导发明的 - <a href='https://www.linkedin.com/in/ying-chan-10077651/'>https://www.linkedin.com/in/ying-chan-10077651/</a>
2. spvtoken 最开始是 sCrypt XiaoHui 写的 - <a href='https://github.com/sCrypt-Inc/boilerplate/blob/master/contracts/spvToken.scrypt'>https://github.com/sCrypt-Inc/boilerplate/blob/master/contracts/spvToken.scrypt</a>
3. notesv 李龙先生做了指导 <a href='https://note.sv'>https://note.sv</a>
4. 参考了感应合约 <a href='https://sensiblecontract.org/'>https://sensiblecontract.org/</a> 里面将锁定脚本做hash处理的思路。
5. microserver 修改于 2021-4-15
*/
/*
token 转账流程
1. 钱包将prevTx发送到SigTxId预言机
2. 钱包在预言机的utxo里面查找刚才对应的tx的utxo,从里面获取 prevPrevTxId, prevTxHash, rabinSigTxId
3. 钱包将prePrevTx 发送到SignTxScript预言机
4. 钱包在预言机的utxo里面查找刚才对应的tx的utxo,从里面获取 preLockTokenScriptHash,prevPrevTxHash,rabinSigTxScript
5. 钱包将自己拥有的token发送到目标地址
思路,xiaohui的spvtoken通俗易懂,但是有交易膨胀问题,那么就将引起膨胀的数据和代码移到预言机去处理
*/
contract SPVToken {
// prevTx: tx being spent by the current tx
// prevPrevTx: tx being spent by prevTx
bytes rabinPubkeySigTx;
bytes rabinPubkeySigTxScript;
public function transfer(Sig senderSig, PubKey receiver, int satoshiAmount, SigHashPreimage txPreimage, bytes prevPrevTxId, bytes prevTxHash, Sig rabinSigTxId, bytes preLockTokenScriptHash, bytes prevPrevTxHash, Sig rabinSigTxScript) {
// 此锁定脚本的主要目的是证明:1. 当前输入的锁定脚本跟当前输出的锁定脚本一致。2. 上一个交易的锁定脚本跟当前的锁定脚本一致。
// constants
int TxIdLen = 32;
int TokenIdLen = TxIdLen;
int PrevTxIdIdx = 5;
// int UnlockingScriptIdx = 41;
// uninitialized token ID
bytes NullTokenId = num2bin(0, TokenIdLen);
require(Tx.checkPreimage(txPreimage));
require(Util.rabinVerify(prevPrevTxId + prevTxHash, rabinSigTxId, this.rabinPubkeySigTx));
require(Util.rabinVerify(preLockTokenScriptHash + prevPrevTxHash, rabinSigTxScript, this.rabinPubkeySigTxScript));
// read previous locking script: codePart + OP_RETURN + tokenID + ownerPublicKey
bytes lockingScript = Util.scriptCode(txPreimage);
int scriptLen = len(lockingScript);
// constant part of locking script: upto OP_RETURN
int constStart = scriptLen - TokenIdLen - Util.PubKeyLen;
bytes constPart = lockingScript[: constStart];
PubKey sender = PubKey(lockingScript[constStart + TokenIdLen : ]);
// authorize
require(checkSig(senderSig, sender));
bytes outpoint = Util.outpoint(txPreimage);
bytes prevTxId = outpoint[ : TokenIdLen];
require(prevTxHash == prevTxId);
bytes tokenId = lockingScript[constStart : constStart + TokenIdLen];
if (tokenId == NullTokenId) {
// get prevTxId and use it to initialize token ID
tokenId = prevTxId;
} else {
/*
* validate not only the parent tx (prevTx), but also its parent tx (prevPrevTx)
*/
// TODO: assume 1 input, to extend to multiple inputs
//bytes prevPrevTxId = prevTx[PrevTxIdIdx : PrevTxIdIdx + TxIdLen];
require(prevPrevTxHash == prevPrevTxId);
require(preLockTokenScriptHash == hash256(constPart + OP_RETURN + tokenId) || preLockTokenScriptHash == hash256(constPart + OP_RETURN + NullTokenId));
}
// validate parent tx
bytes outputScript = constPart + tokenId + receiver;
bytes output = Util.buildOutput(outputScript, satoshiAmount);
require(hash256(output) == Util.hashOutputs(txPreimage));
}
}
contract OracleSigTxId {
// 1. 钱包将需要解析的tx发送给预言机,解锁脚本的数据区为OP_RETURN + tx + txLen + oraclePubkey
// 2. 预言机收到tx后,解析出输入的TxId(prevTxId)和整个tx的txHash,对解析结果进行rabin签名。
// 3. 预言机将 prevTxId, txHash,rabin签名作为解锁参数,解锁预言机tx。
// 4. 预言机拿到奖励,同时将签名结果输出到op_return。
// 5. orcale 需要运行一个侦听tx的脚本,只要是发往自己的tx,并且锁定脚本的codePart部分等于签名脚本,而且最后是oracle的pubkey,就进行tx签名
bytes rabinPubkeySigTxId;
public function sign(Sig ownerSign, SigHashPreimage txPreimage, sig rabinSigTxId, int satoshiAmount) {
// constants
int TxIdLen = 32;
int PrevTxIdIdx = 5;
require(Tx.checkPreimage(txPreimage));
// read previous locking script: codePart + OP_RETURN + tx + txLen + oraclePubkey
bytes lockingScript = Util.scriptCode(txPreimage);
int scriptLen = len(lockingScript);
int txLen = Util.readVarintLen(lockingScript[scriptLen - Util.PubKeyLen - 2:]);
bytes tx = lockingScript[scriptLen - Util.PubKeyLen - 2 - txLen : scriptLen - Util.PubKeyLen - 2];
PubKey ownerPubkey = PubKey(lockingScript[scriptLen - Util.PubKeyLen : ]);
// authorize
require(checkSig(ownerSign, ownerPubkey));
bytes prevTxId = tx[PrevTxIdIdx : PrevTxIdIdx + TxIdLen];
bytes txHash = hash256(tx);
require(Util.rabinVerify(prevTxId + txHash, rabinSigTxId, this.rabinPubkeySigTxId))
// create output
bytes output0 = Util.buildOutput(OP_RETURN + prevTxId + txHash + rabinSigTxId, 0); // 预言机的结果
bytes output1 = Util.buildOutput(buildPublicKeyHashScript(hash160(ownerPubkey)), satoshiAmount); // 预言机的奖励
require(hash256(output0 + output1) == Util.hashOutputs(txPreimage));
}
}
contract OracleSigTxScript {
// 1. 钱包将需要解析的tx发送给预言机,解锁脚本的数据区为OP_RETURN + tx + txLen + oraclePubkey
// 2. 预言机收到tx后,解析出输出的锁定脚本hash(即 lockTokenScriptHash)和整个tx的 txHash,对解析结果进行rabin签名。
// 3. 预言机将 lockTokenScriptHash, txHash,rabin签名作为解锁参数,解锁预言机tx。
// 4. 预言机拿到奖励,同时将签名结果输出到op_return。
// 5. 预言机需要运行一个侦听tx的脚本,只要是发往自己的tx,并且锁定脚本的codePart部分等于签名脚本,而且最后是oracle的pubkey,就进行tx签名
// 注意:lockTokenScriptHash = hash(codePart + OP_RETURN + tokenId)
bytes rabinPubkeySigTxScript;
public function sign(Sig ownerSign, SigHashPreimage txPreimage, sig rabinSigTxScript, int satoshiAmount) {
// constants
int TxIdLen = 32;
int TokenIdLen = TxIdLen;
require(Tx.checkPreimage(txPreimage));
// read previous locking script: codePart + OP_RETURN + tx + txLen + oraclePubkey
bytes lockingScript = Util.scriptCode(txPreimage);
int scriptLen = len(lockingScript);
int txLen = Util.readVarintLen(lockingScript[scriptLen - Util.PubKeyLen - 2:]);
bytes tx = lockingScript[scriptLen - Util.PubKeyLen - 2 - txLen : scriptLen - Util.PubKeyLen - 2];
PubKey ownerPubkey = PubKey(lockingScript[scriptLen - Util.PubKeyLen : ]);
// authorize
require(checkSig(ownerSign, ownerPubkey));
// token的锁定脚本是 codePart + OP_RETURN + tokenID + ownerPublicKey,我们从中取出 codePart + OP_RETURN + tokenID
bytes output = Util.Output(tx, 0);
bytes lockTokenScriptHash = hash256(output[:len(output) - Util.PubKeyLen]);
bytes txHash = hash256(tx);
require(Util.rabinVerify(lockTokenScriptHash + txHash, rabinSigTxScript, this.rabinPubkeyPrevprev))
// create output
bytes output0 = Util.buildOutput(OP_RETURN + lockTokenScriptHash + txHash + rabinSigTxScript, 0);
bytes output1 = Util.buildOutput(buildPublicKeyHashScript(hash160(ownerPubkey)), satoshiAmount);
require(hash256(output0 + output1) == Util.hashOutputs(txPreimage));
}
}
改良版本
import "util.scrypt";
/**
* A non-fungible token enforced by miners at layer 1
*/
/*
Copyright
1. OP_PUSH_TX是nChain的研究员Ying Chan主导发明的 - <a href='https://www.linkedin.com/in/ying-chan-10077651/'>https://www.linkedin.com/in/ying-chan-10077651/</a>
2. spvtoken 最开始是 sCrypt XiaoHui 写的 - <a href='https://github.com/sCrypt-Inc/boilerplate/blob/master/contracts/spvToken.scrypt'>https://github.com/sCrypt-Inc/boilerplate/blob/master/contracts/spvToken.scrypt</a>
3. notesv 李龙先生做了指导 <a href='https://note.sv'>https://note.sv</a>
4. 参考了感应合约 <a href='https://sensiblecontract.org/'>https://sensiblecontract.org/</a> 里面将锁定脚本做hash处理的思路。
5. microserver 修改于 2021-4-15
*/
/*
token 转账流程
1. 钱包将prevTx发送到SigTxId预言机
2. 钱包在预言机的utxo里面查找刚才对应的tx的utxo,从里面获取 prevPrevTxId, prevTxHash, rabinSigTxId
3. 钱包将prePrevTx 发送到SignTxScript预言机
4. 钱包在预言机的utxo里面查找刚才对应的tx的utxo,从里面获取 preLockTokenScriptHash,prevPrevTxHash,rabinSigTxScript
5. 钱包将自己拥有的token发送到目标地址
思路,xiaohui的spvtoken通俗易懂,但是有交易膨胀问题,那么就将引起膨胀的数据和代码移到预言机去处理
*/
contract SPVToken {
// prevTx: tx being spent by the current tx
// prevPrevTx: tx being spent by prevTx
bytes rabinPubkeySigTx;
bytes rabinPubkeySigTxScript;
public function transfer(Sig senderSig, PubKey receiver, int satoshiAmount, SigHashPreimage txPreimage, bytes prevPrevTxId, bytes prevTxHash, Sig rabinSigTxId, bytes preLockTokenScriptHash, bytes prevPrevTxHash, Sig rabinSigTxScript) {
// 此锁定脚本的主要目的是证明:1. 当前输入的锁定脚本跟当前输出的锁定脚本一致。2. 上一个交易的锁定脚本跟当前的锁定脚本一致。
// constants
int TxIdLen = 32;
int TokenIdLen = TxIdLen;
int PrevTxIdIdx = 5;
// int UnlockingScriptIdx = 41;
// uninitialized token ID
bytes NullTokenId = num2bin(0, TokenIdLen);
require(Tx.checkPreimage(txPreimage));
require(Util.rabinVerify(prevPrevTxId + prevTxHash, rabinSigTxId, this.rabinPubkeySigTx));
require(Util.rabinVerify(preLockTokenScriptHash + prevPrevTxHash, rabinSigTxScript, this.rabinPubkeySigTxScript));
// read previous locking script: codePart + OP_RETURN + tokenID + ownerPublicKey
bytes lockingScript = Util.scriptCode(txPreimage);
int scriptLen = len(lockingScript);
// constant part of locking script: upto OP_RETURN
int constStart = scriptLen - TokenIdLen - Util.PubKeyLen;
bytes constPart = lockingScript[: constStart];
PubKey sender = PubKey(lockingScript[constStart + TokenIdLen : ]);
// authorize
require(checkSig(senderSig, sender));
bytes outpoint = Util.outpoint(txPreimage);
bytes prevTxId = outpoint[ : TokenIdLen];
require(prevTxHash == prevTxId);
bytes tokenId = lockingScript[constStart : constStart + TokenIdLen];
if (tokenId == NullTokenId) {
// get prevTxId and use it to initialize token ID
tokenId = prevTxId;
} else {
/*
* validate not only the parent tx (prevTx), but also its parent tx (prevPrevTx)
*/
// TODO: assume 1 input, to extend to multiple inputs
//bytes prevPrevTxId = prevTx[PrevTxIdIdx : PrevTxIdIdx + TxIdLen];
require(prevPrevTxHash == prevPrevTxId);
require(preLockTokenScriptHash == hash256(constPart + OP_RETURN + tokenId) || preLockTokenScriptHash == hash256(constPart + OP_RETURN + NullTokenId));
}
// validate parent tx
bytes outputScript = constPart + tokenId + receiver;
bytes output = Util.buildOutput(outputScript, satoshiAmount);
require(hash256(output) == Util.hashOutputs(txPreimage));
}
}
contract OracleSigTxId {
// 1. 钱包将需要解析的tx发送给预言机,解锁脚本的数据区为OP_RETURN + tx + txLen + oraclePubkey
// 2. 预言机收到tx后,解析出输入的TxId(prevTxId)和整个tx的txHash,对解析结果进行rabin签名。
// 3. 预言机将 prevTxId, txHash,rabin签名作为解锁参数,解锁预言机tx。
// 4. 预言机拿到奖励,同时将签名结果输出到op_return。
// 5. orcale 需要运行一个侦听tx的脚本,只要是发往自己的tx,并且锁定脚本的codePart部分等于签名脚本,而且最后是oracle的pubkey,就进行tx签名
bytes rabinPubkeySigTxId;
public function sign(Sig ownerSign, SigHashPreimage txPreimage, sig rabinSigTxId, int satoshiAmount) {
// constants
int TxIdLen = 32;
int PrevTxIdIdx = 5;
require(Tx.checkPreimage(txPreimage));
// read previous locking script: codePart + OP_RETURN + tx + txLen + oraclePubkey
bytes lockingScript = Util.scriptCode(txPreimage);
int scriptLen = len(lockingScript);
int txLen = Util.readVarintLen(lockingScript[scriptLen - Util.PubKeyLen - 2:]);
bytes tx = lockingScript[scriptLen - Util.PubKeyLen - 2 - txLen : scriptLen - Util.PubKeyLen - 2];
PubKey ownerPubkey = PubKey(lockingScript[scriptLen - Util.PubKeyLen : ]);
// authorize
require(checkSig(ownerSign, ownerPubkey));
bytes prevTxId = tx[PrevTxIdIdx : PrevTxIdIdx + TxIdLen];
bytes txHash = hash256(tx);
require(Util.rabinVerify(prevTxId + txHash, rabinSigTxId, this.rabinPubkeySigTxId))
// create output
bytes output0 = Util.buildOutput(OP_RETURN + prevTxId + txHash + rabinSigTxId, 0); // 预言机的结果
bytes output1 = Util.buildOutput(buildPublicKeyHashScript(hash160(ownerPubkey)), satoshiAmount); // 预言机的奖励
require(hash256(output0 + output1) == Util.hashOutputs(txPreimage));
}
}
contract OracleSigTxScript {
// 1. 钱包将需要解析的tx发送给预言机,解锁脚本的数据区为OP_RETURN + tx + txLen + oraclePubkey
// 2. 预言机收到tx后,解析出输出的锁定脚本hash(即 lockTokenScriptHash)和整个tx的 txHash,对解析结果进行rabin签名。
// 3. 预言机将 lockTokenScriptHash, txHash,rabin签名作为解锁参数,解锁预言机tx。
// 4. 预言机拿到奖励,同时将签名结果输出到op_return。
// 5. 预言机需要运行一个侦听tx的脚本,只要是发往自己的tx,并且锁定脚本的codePart部分等于签名脚本,而且最后是oracle的pubkey,就进行tx签名
// 注意:lockTokenScriptHash = hash(codePart + OP_RETURN + tokenId)
bytes rabinPubkeySigTxScript;
public function sign(Sig ownerSign, SigHashPreimage txPreimage, sig rabinSigTxScript, int satoshiAmount) {
// constants
int TxIdLen = 32;
int TokenIdLen = TxIdLen;
require(Tx.checkPreimage(txPreimage));
// read previous locking script: codePart + OP_RETURN + tx + txLen + oraclePubkey
bytes lockingScript = Util.scriptCode(txPreimage);
int scriptLen = len(lockingScript);
int txLen = Util.readVarintLen(lockingScript[scriptLen - Util.PubKeyLen - 2:]);
bytes tx = lockingScript[scriptLen - Util.PubKeyLen - 2 - txLen : scriptLen - Util.PubKeyLen - 2];
PubKey ownerPubkey = PubKey(lockingScript[scriptLen - Util.PubKeyLen : ]);
// authorize
require(checkSig(ownerSign, ownerPubkey));
// token的锁定脚本是 codePart + OP_RETURN + tokenID + ownerPublicKey,我们从中取出 codePart + OP_RETURN + tokenID
bytes output = Util.Output(tx, 0);
bytes lockTokenScriptHash = hash256(output[:len(output) - Util.PubKeyLen]);
bytes txHash = hash256(tx);
require(Util.rabinVerify(lockTokenScriptHash + txHash, rabinSigTxScript, this.rabinPubkeyPrevprev))
// create output
bytes output0 = Util.buildOutput(OP_RETURN + lockTokenScriptHash + txHash + rabinSigTxScript, 0);
bytes output1 = Util.buildOutput(buildPublicKeyHashScript(hash160(ownerPubkey)), satoshiAmount);
require(hash256(output0 + output1) == Util.hashOutputs(txPreimage));
}
}