- 概要
- 非対称暗号化と署名
- 秘密鍵と公開鍵
- 取引の概要
- 取引出力
- 取引入力
- トランザクションデータ構造
- トランザクションID
- トランザクション署名
- いいえ消費者取引を出力しません
- 取引出力未消費の在庫更新
- トランザクションの検証
- 元のトランザクション
- テスト経験
- 概要
概要
この章では、暗号通貨紹介します取引メカニズムを。トランザクションの後にこのメカニズムでは、我々は、金融システムへの暗号化のゴージャスなだけで基本的な機能からブロックチェーンブロックチェーンをオンにします。最終的には、アドレス、および該当するユーザーが指定したターゲット・ユーザーによる為替取引を暗号化することができるようになります。もちろん、取引前にあなたは、通貨の保有者であることを証明できなければなりません。
この目標を達成するために、我々はまた、概念の多くを知っている必要があります。そのような非対称暗号化、署名、トランザクション入力(開始剤であることが理解される)とトランザクション出力(受信者であると理解される)など。
非対称暗号化と署名
秘密鍵のペア、公開鍵は控除によって生成されますが、秘密鍵は公開鍵から推定することができない秘密鍵 - 非対称暗号化では、パブリックになります。その名の鍵、公開鍵は安全に他の人と共有することができますが、秘密鍵は自分自身だけを保存することができます。
任意のメッセージは、署名を生成するための秘密鍵によって暗号化することができます。対応する公開鍵を持っていれば誰でも、公開することにより、情報の暗号化を解除した後、署名の有効性を検証することができます。
ここでは、と呼ばれる使用する楕円非対称暗号、楕円曲線暗号化アルゴリズム(ECDSA)でライブラリを達成するためのライブラリを。
全体的に、私たちの金融システムは、異なる目的を達成するための暗号化アルゴリズムの異なる2組を暗号化するために使用されます。
- SHA256:ワークロードの証拠のブロックをハッシュ化し、データの整合性を確保するために変更することはできません。
- 非対称暗号化:ユーザ認証。
秘密鍵と公開鍵
ECDSA有効な秘密鍵は、例えば32バイトの文字列、次のとおりです。
19f128debc1b9122da0635954488b208b829879cf13b3d6cac5d1260c0fd967c
公開鍵は、例えば、64バイトの文字列に続いて「04」の開始時に有効です。
04bfcab8722991ae774db48f934ca79cfb7dd991229153b9f732ba5334aafcd8e7266e47076996b55a14bf9913ee3145ce0cfc1372ada8ada74bd287450313534a
秘密鍵の解釈から公開鍵を生成します。公開鍵は、トランザクションの受信者(すなわち、受信者のアドレス)として使用されます。
取引の概要
我々はすべてのコードを記述する前に、トランザクションの構造は一般的な理解になりましょう。入力と出力:取引は、2つの部分で構成されます。出力は、通貨の取引を暗号化し、受信者(以上があるかもしれない)、入力が取引のために使用される通貨が実際にあることを証明するために使用されて存在し、トランザクションの送信者によって開催されているを指定します。各項目はアドレスがこの契約に署名する(つまり、ユーザーの公開鍵に代わって、ある)指さ取るシステムの出力を見ることができるこの章の後の既存の「未消費出力」に、取引の入力ポイントの下になります彼らはロックを解除することができた場合、復号化され、それは出力がユーザーに属するん表します。
取引出力
アドレスと番号2のメンバ変数によって取引出力(TXOUT)構造。数は、仮想通貨取引の数を表します。ECDSAアドレスが受信者に代わって、公開鍵、です。それだけで個人ユーザーが暗号化され、対応する通貨にアクセスすることができ、対応するアドレスを有することを意味します。
class TxOut {
public address: string;
public amount: number;
constructor(address: string, amount: number) {
this.address = address;
this.amount = amount;
}
}
取引入力
取引入力(TXIN)アーキテクチャは、情報の暗号化通貨の源を提供します。TXINはtxOutId以前のトランザクション(トランザクションID)によって指示される各トランザクションの暗号通貨が以前のトランザクションからロック解除され、通貨は、ロック解除できるようになり、出力で受信者に送信されます。この構造のシグネチャ特性は、送信者に暗号化署名自分の秘密鍵で(トランザクションIDの署名データ)を、出力の単一のトランザクションポイントによって使用前に署名に対応するアドレス(即ち公開鍵)であります送信者が元のトランザクションの所有者との取引にとがっていることを証明することが可能であることを確認します。
class TxIn {
public txOutId: string;
public txOutIndex: number;
public signature: string;
}
これが唯一の署名秘密鍵ではなく、秘密鍵自体によって保存されることに注意してください。システムブロックチェーン全体で、そこだけ公開鍵と署名があり、プライベートではありません。
一般的に、我々は、対応する暗号通貨のロックを解除するための入力、および出力が通貨を再ロックし、受信者が新しい所有者になることを許可すると言うことができます。
トランザクションデータ構造
良い取引の入力と出力の上記の定義は、取引自体のデータ構造は非常に簡単になったとき。
class Transaction {
public id: string;
public txIns: TxIn[];
public txOuts: TxOut[];
}
トランザクションID
トランザクションは、計算されたコンテンツ作られたハッシュのトランザクションデータ構造により、トランザクションの一意のIDを表します。ここで我々は、開始剤の署名が含まれていないことに注意しなければならない、これは後に追加されます。
const getTransactionId = (transaction: Transaction): string => {
const txInContent: string = transaction.txIns
.map((txIn: TxIn) => txIn.txOutId + txIn.txOutIndex)
.reduce((a, b) => a + b, '');
const txOutContent: string = transaction.txOuts
.map((txOut: TxOut) => txOut.address + txOut.amount)
.reduce((a, b) => a + b, '');
return CryptoJS.SHA256(txInContent + txOutContent).toString();
};
トランザクション署名
通过签名来保证交易内容不被修改是非常重要的。因为交易都是被公开的,任何人都能够访问所有的交易,就算这些交易还没有来得及加入到区块链当中去。
当对交易进行签名时,事实上我们只会对交易id进行签名。也就是说,参考前面的交易id的生成,只要交易中任何的一项内容发生变化,交易id都会发生变化, 然后相应的对交易id的签名也就会改变,导致整个交易无效。
const signTxIn = (transaction: Transaction, txInIndex: number,
privateKey: string, aUnspentTxOuts: UnspentTxOut[]): string => {
const txIn: TxIn = transaction.txIns[txInIndex];
const dataToSign = transaction.id;
const referencedUnspentTxOut: UnspentTxOut = findUnspentTxOut(txIn.txOutId, txIn.txOutIndex, aUnspentTxOuts);
const referencedAddress = referencedUnspentTxOut.address;
const key = ec.keyFromPrivate(privateKey, 'hex');
const signature: string = toHexString(key.sign(dataToSign).toDER());
return signature;
};
这里我们看下如果发生攻击时,交易签名是如何对我们的系统进行保护的:
- 攻击者节点收到一个交易广播:从AAA地址中发送10个币到BBB,交易id为0x555...
- 攻击者随后将接收者地址修改成CCC, 然后把这个交易继续发送到网络中。现在这个广播内容就会变成:从AAA地址中发送10个币到CCC
- 但是,因为交易数据中的接收者地址被修改了,对应的交易id就不再有效。新的交易id应该会改变,比如变成0x567...。 当其他用户收到该交易时,首先会验证交易id,立即就会发现这个数据被篡改了。
- 如果攻击者修改接收地址的同时,把交易id也修改了呢?因为AAA只是对交易原始id 0x555...进行签名,并没有用私钥对0x567...进行签名, 其他节点验证时即能识破
- 所以最终该篡改的交易都不会被其他节点接受,因为无论怎么修改,它都是无效的。
未消费的交易outputs
一笔交易中,发起者必须在input中指定还没有被消费的交易output。 你在区块链中拥有的加密货币, 指的其实就是在未消费交易outputs中,接受者地址为自己的公钥的一系列outputs。
当对交易进行有效性验证时,我们只需要关注未消费交易outputs这份清单。当然,这份独立的清单也可以从我们的区块链中演绎出来。这样子实现的话,当我们把交易纳入到区块链中时,我们将同时也会更新未消费交易outputs这份清单。
未消费交易output的数据结构大致如下所示:
class UnspentTxOut {
public readonly txOutId: string;
public readonly txOutIndex: number;
public readonly address: string;
public readonly amount: number;
constructor(txOutId: string, txOutIndex: number, address: string, amount: number) {
this.txOutId = txOutId;
this.txOutIndex = txOutIndex;
this.address = address;
this.amount = amount;
}
}
整一个清单其实就是一个数组:
let unspentTxOuts: UnspentTxOut[] = [];
未消费交易outputs清单更新
每当一个新的区块加入到区块链中,我们都必须对我们的未消费outputs进行更新。 因为新的区块将可能会消费掉「未消费outputs」中的一些outputs,并肯定会引入新的outputs。
为了对此进行处理,我们需要在新区块加入时,将区块的交易数据中的的未消费交易outputs给解析出来:
const newUnspentTxOuts: UnspentTxOut[] = newTransactions
.map((t) => {
return t.txOuts.map((txOut, index) => new UnspentTxOut(t.id, index, txOut.address, txOut.amount));
})
.reduce((a, b) => a.concat(b), []);
同时,我们还要找出这个新增区块将会消耗掉哪些未交易outputs。我们通过检验交易数据的inputs下的项,即可以将这些数据找出来:
const consumedTxOuts: UnspentTxOut[] = newTransactions
.map((t) => t.txIns)
.reduce((a, b) => a.concat(b), [])
.map((txIn) => new UnspentTxOut(txIn.txOutId, txIn.txOutIndex, '', 0));
最终我们通过删除已经消费的并且加上新的未消费的,从而产生了新的未消费交易outputs,具体代码如下:
const resultingUnspentTxOuts = aUnspentTxOuts
.filter(((uTxO) => !findUnspentTxOut(uTxO.txOutId, uTxO.txOutIndex, consumedTxOuts)))
.concat(newUnspentTxOuts);
以上代码片段都是在updateUnspentTxOuts这个方法中实现的。 需要注意的是,这个方法只有在区块含有的交易数据(以及这个块本身)都被验证没有问题的时候才会被调用。
交易有效性验证
现在我们可以制定一些规则来检查交易是否有效:
- 交易数据结构有效性验证
对数据结构的类型等进行验证:
const isValidTransactionStructure = (transaction: Transaction) => {
if (typeof transaction.id !== 'string') {
console.log('transactionId missing');
return false;
}
...
//check also the other members of class
}
- 交易id有效性验证
if (getTransactionId(transaction) !== transaction.id) {
console.log('invalid tx id: ' + transaction.id);
return false;
}
- inputs有效性验证
交易数据结构中的inputs中的签名必须有效,且指向的交易来源outputs必须还没有被消费掉。
const validateTxIn = (txIn: TxIn, transaction: Transaction, aUnspentTxOuts: UnspentTxOut[]): boolean => {
const referencedUTxOut: UnspentTxOut =
aUnspentTxOuts.find((uTxO) => uTxO.txOutId === txIn.txOutId && uTxO.txOutId === txIn.txOutId);
if (referencedUTxOut == null) {
console.log('referenced txOut not found: ' + JSON.stringify(txIn));
return false;
}
const address = referencedUTxOut.address;
const key = ec.keyFromPublic(address, 'hex');
return key.verify(transaction.id, txIn.signature);
};
- outputs有效性验证
交易数据结构中outputs所指定的交易总额之和,必须与inputs中指向的交易来源的outputs总额之和一致。比如,指向的交易来源output有50个币,然后你需要给别人发送30个币,最终outputs中将会有两条记录,一条是发送30个币给对方,另外一条是发送20个币给自己,总共加起来就是50个币(当然,最终在未消费交易outputs中,交易来源的这条output将会被删除掉,而新的两个outputs将会被加进去)。
const totalTxInValues: number = transaction.txIns
.map((txIn) => getTxInAmount(txIn, aUnspentTxOuts))
.reduce((a, b) => (a + b), 0);
const totalTxOutValues: number = transaction.txOuts
.map((txOut) => txOut.amount)
.reduce((a, b) => (a + b), 0);
if (totalTxOutValues !== totalTxInValues) {
console.log('totalTxOutValues !== totalTxInValues in tx: ' + transaction.id);
return false;
}
原始交易
如前面谈及的,交易的inputs所指向的交易来源总是来自「未消费交易outputs」, 但是,最原始的一条交易记录的inputs将会没地方指向。因为这时候还根本没有任何未消费交易outputs。为了解决这个问题,我们需要引入一个特殊的交易类型:「原始交易」
原始交易的数据结构中只会有一个output,且input不会指向任何交易来源。也就是说这种交易只是为了增加新币进行流通用的,并不是一个用户和另外一个用户进行的交易。每一次挖矿成功,都会产生一个原始交易。
我们对原始交易中产生的货币量定义为50个币:
const COINBASE_AMOUNT: number = 50;
每个区块的第一个交易记录都是原始交易,且该第一个交易中的output中指向的接收者地址都是挖出该区块的矿工的公钥。所以,原始交易可以堪称是对挖矿的一种激励机制:一旦你挖出了一个区块,你就会获得50个币的激励。
同时,我们会将区块的高度信息(可以理解为区块的序号)加入到原始交易的input当中,这样做的目的是为了保证每笔原始交易id都是不一样的。因为交易id是通过对交易的内容做哈希算出来的,多条‘给地址0x5cc发放50个币‘记录将不至于会生成同一个交易id。
对原始交易的有效性验证将会和对普通交易的有效性验证有所不同:
const validateCoinbaseTx = (transaction: Transaction, blockIndex: number): boolean => {
if (getTransactionId(transaction) !== transaction.id) {
console.log('invalid coinbase tx id: ' + transaction.id);
return false;
}
if (transaction.txIns.length !== 1) {
console.log('one txIn must be specified in the coinbase transaction');
return;
}
if (transaction.txIns[0].txOutIndex !== blockIndex) {
console.log('the txIn index in coinbase tx must be the block height');
return false;
}
if (transaction.txOuts.length !== 1) {
console.log('invalid number of txOuts in coinbase transaction');
return false;
}
if (transaction.txOuts[0].amount != COINBASE_AMOUNT) {
console.log('invalid coinbase amount in coinbase transaction');
return false;
}
return true;
};
测试体验
因为当前还没有引入钱包机制,手动测试将会非常困难,所以不建议现在进行测试体验。 在下一章节中实现了钱包之后,体验起来就会方便很多了。
小结
这个章节中我们将交易引入到我们的区块链中来。基本思路是很简单的:我们通过在交易数据结构中的inputs中指定「未消费outputs」来作为交易货币来源,并通过「未消费outputs」中的接收者地址来对本交易的签名进行验证,来证明该交易的发起者是该未消费outputs持有者,然后通过outputs中的接收者的地址来将该未消费outputs重新分配给指定的接收者,最终交易完成。
しかし、今までは、契約を作成するためには、まだかなり面倒です。私たちは、手動でトランザクションの入力と出力を作成し、トランザクションの署名を暗号化するために私たちの秘密鍵を使用する必要があります。私たちの次のセクションでは、我々は財布の仕組みをご紹介します、これらの困難は11だった壊れます。
表示するには、このセクションを完了してくださいここに
承認を得て転載世界評議会Fenduo珠海、によってコンパイルされたこの記事は、ポイントなどのような、星を与えるのGithub上で、このようなプロジェクトとして、Tucaoをコメントしてください、非常に高く評価されます。