Commit fd2ee013 authored by David Vorick's avatar David Vorick

switch to variable public keys

parent 954a935e
......@@ -15,31 +15,11 @@ import (
// more signatures are needed.
type InputSignatures struct {
RemainingSignatures uint64
PossibleKeys []PublicKey
PossibleKeys []SiaPublicKey
UsedKeys map[uint64]struct{}
Index int
}
// validStorageProofs checks that a transaction follows the limitations placed
// on transactions with storage proofs.
func (t Transaction) validStorageProofs() bool {
if len(t.StorageProofs) == 0 {
return true
}
if len(t.Outputs) != 0 {
return false
}
if len(t.FileContracts) != 0 {
return false
}
if len(t.SiafundOutputs) != 0 {
return false
}
return true
}
// sortedUnique checks that all of the elements in a []unit64 are sorted and
// without repeates, and also checks that the largest element is less than or
// equal to the biggest allowed element.
......@@ -124,6 +104,8 @@ func (t Transaction) validCoveredFields() (err error) {
return
}
// validSignatures takes a transaction and returns an error if the signatures
// are not all valid.
func (s *State) validSignatures(t Transaction) (err error) {
// Check that all covered fields objects follow the rules.
err = t.validCoveredFields()
......@@ -138,6 +120,7 @@ func (s *State) validSignatures(t Transaction) (err error) {
if exists {
return errors.New("output spent twice in the same transaction.")
}
inSig := &InputSignatures{
RemainingSignatures: input.SpendConditions.NumSignatures,
PossibleKeys: input.SpendConditions.PublicKeys,
......@@ -157,14 +140,38 @@ func (s *State) validSignatures(t Transaction) (err error) {
if exists {
return errors.New("one public key was used twice while signing an input")
}
// Check that the timelock has expired.
if sig.TimeLock > s.height() {
return errors.New("signature used before timelock expiration")
}
// Check that the signature matches the public key + data.
sigHash := t.SigHash(i)
if !crypto.VerifyBytes(sigHash[:], sigMap[sig.InputID].PossibleKeys[sig.PublicKeyIndex], sig.Signature) {
return errors.New("signature is invalid")
// Check that the signature verifies. Sia is built to support multiple
// types of signature algorithms, this is handled by the switch
// statement.
publicKey := sigMap[sig.InputID].PossibleKeys[sig.PublicKeyIndex]
switch publicKey.Algorithm {
case ED25519Identifier:
// Check that the public key is a legal length.
if len(publicKey.Key) != crypto.PublicKeySize {
return errors.New("public key is invalid")
}
if len(sig.Signature) != crypto.SignatureSize {
return errors.New("signature is invalid")
}
// Decode the public key and signature from the data types.
var decodedPK crypto.PublicKey
var decodedSig crypto.Signature
copy(decodedPK[:], publicKey.Key)
copy(decodedSig[:], sig.Signature)
sigHash := t.SigHash(i)
if !crypto.VerifyBytes(sigHash[:], decodedPK, decodedSig) {
return errors.New("signature is invalid")
}
default:
return errors.New("public key algorithm not recognized")
}
// Subtract the number of signatures remaining for this input.
......@@ -181,6 +188,9 @@ func (s *State) validSignatures(t Transaction) (err error) {
return nil
}
// ValidSignatures takes a transaction and determines whether the transaction
// contains a legal set of signatures, including checking the timelocks against
// the current state height.
func (s *State) ValidSignatures(t Transaction) error {
s.mu.RLock()
defer s.mu.RUnlock()
......
......@@ -4,6 +4,26 @@ import (
"errors"
)
// validStorageProofs checks that a transaction follows the limitations placed
// on transactions with storage proofs.
func (t Transaction) validStorageProofs() bool {
if len(t.StorageProofs) == 0 {
return true
}
if len(t.Outputs) != 0 {
return false
}
if len(t.FileContracts) != 0 {
return false
}
if len(t.SiafundOutputs) != 0 {
return false
}
return true
}
// validInput returns err = nil if the input is valid within the current state,
// otherwise returns an error explaining what wasn't valid.
func (s *State) validInput(input Input) (err error) {
......
......@@ -14,7 +14,6 @@ package consensus
// coverage.
import (
"github.com/NebulousLabs/Sia/crypto"
"github.com/NebulousLabs/Sia/encoding"
"github.com/NebulousLabs/Sia/hash"
)
......@@ -25,7 +24,9 @@ type (
Currency uint64
Siafund uint64
Identifier [16]byte
Identifier [16]byte
Signature []byte
BlockID hash.Hash
OutputID hash.Hash
ContractID hash.Hash
......@@ -35,6 +36,12 @@ type (
var ZeroAddress = CoinAddress{0}
var FileContractIdentifier = Identifier{'f', 'i', 'l', 'e', ' ', 'c', 'o', 'n', 't', 'r', 'a', 'c', 't'}
var SiacoinOutputIdentifier = Identifier{'s', 'i', 'a', 'c', 'o', 'i', 'n', ' ', 'o', 'u', 't', 'p', 'u', 't'}
var SiafundOutputIdentifier = Identifier{'s', 'i', 'a', 'f', 'u', 'n', 'd', ' ', 'o', 'u', 't', 'p', 'u', 't'}
var ED25519Identifier = Identifier{'e', 'd', '2', '5', '5', '1', '9'}
// A Block contains all of the changes to the state that have occurred since
// the previous block. There are constraints that make it difficult and
// rewarding to find a block.
......@@ -80,7 +87,7 @@ type Input struct {
// to expose the TimeLock and NumSigantures.
type SpendConditions struct {
TimeLock BlockHeight
PublicKeys []crypto.PublicKey
PublicKeys []SiaPublicKey
NumSignatures uint64
}
......@@ -102,9 +109,9 @@ type FileContract struct {
MissedProofAddress CoinAddress
}
// A storage proof contains a segment and the HashSet required to prove that
// the segment is a part of the data in the FileMerkleRoot of the FileContract
// that the storage proof fulfills.
// A StorageProof contains a segment and the HashSet required to prove that the
// segment is a part of the data in the FileMerkleRoot of the FileContract that
// the storage proof fulfills.
type StorageProof struct {
ContractID ContractID
Segment [hash.SegmentSize]byte
......@@ -130,6 +137,16 @@ type SiafundOutput struct {
ClaimStart Currency
}
// A SiaPublicKey is a public key prefixed by an identifier. The identifier
// details the algorithm used for sigining and verification, and the byte slice
// contains the actual public key. Doing things this way makes it easy to
// support multiple types of sigantures, and makes it easier to hardfork new
// signatures into the codebase.
type SiaPublicKey struct {
Algorithm Identifier
Key []byte
}
// A TransactionSignature signs a single input to a transaction to help fulfill
// the unlock conditions of the transaction. It points to an input, a
// particular public key, has a timelock, and also indicates which parts of the
......@@ -139,7 +156,7 @@ type TransactionSignature struct {
PublicKeyIndex uint64
TimeLock BlockHeight
CoveredFields CoveredFields
Signature crypto.Signature
Signature Signature
}
// The CoveredFields portion of a signature indicates which fields in the
......@@ -211,7 +228,7 @@ func (b Block) MinerPayoutID(i int) OutputID {
// contract" and the index of the contract.
func (t Transaction) FileContractID(i int) ContractID {
return ContractID(hash.HashBytes(encoding.MarshalAll(
Identifier{'f', 'i', 'l', 'e', ' ', 'c', 'o', 'n', 't', 'r', 'a', 'c', 't'},
FileContractIdentifier,
t.Inputs,
t.MinerFees,
t.Outputs,
......@@ -229,7 +246,7 @@ func (t Transaction) FileContractID(i int) ContractID {
// and then appending the string "siacoin output" and the index of the output.
func (t Transaction) OutputID(i int) OutputID {
return OutputID(hash.HashBytes(encoding.MarshalAll(
Identifier{'s', 'i', 'a', 'c', 'o', 'i', 'n', ' ', 'o', 'u', 't', 'p', 'u', 't'},
SiacoinOutputIdentifier,
t.Inputs,
t.MinerFees,
t.Outputs,
......@@ -256,7 +273,7 @@ func (fcID ContractID) StorageProofOutputID(proofValid bool) (outputID OutputID)
// index `i` in the transaction.
func (t Transaction) SiafundOutputID(i int) OutputID {
return OutputID(hash.HashBytes(encoding.MarshalAll(
Identifier{'s', 'i', 'a', 'f', 'u', 'n', 'd', ' ', 'o', 'u', 't', 'p', 'u', 't'},
SiafundOutputIdentifier,
t.Inputs,
t.MinerFees,
t.Outputs,
......
......@@ -6,10 +6,11 @@ import (
"github.com/agl/ed25519"
)
// One thing that worries me about this file is that the library returns a
// bunch of pointers to data, but the types copy the data into other memory and
// pass that around instead. This may result in side channel attacks becoming
// possible.
const (
PublicKeySize = ed25519.PublicKeySize
SecretKeySize = ed25519.PrivateKeySize
SignatureSize = ed25519.SignatureSize
)
type (
PublicKey *[ed25519.PublicKeySize]byte
......
......@@ -16,8 +16,13 @@ func (w *Wallet) timelockedCoinAddress(unlockHeight consensus.BlockHeight) (coin
spendConditions = consensus.SpendConditions{
TimeLock: unlockHeight,
NumSignatures: 1,
PublicKeys: []crypto.PublicKey{pk},
PublicKeys: []consensus.SiaPublicKey{
consensus.SiaPublicKey{
Algorithm: consensus.ED25519Identifier,
},
},
}
copy(spendConditions.PublicKeys[0].Key, pk[:])
coinAddress = spendConditions.CoinAddress()
// Create a spendableAddress for the keys and add it to the
......@@ -60,8 +65,13 @@ func (w *Wallet) coinAddress() (coinAddress consensus.CoinAddress, spendConditio
}
spendConditions = consensus.SpendConditions{
NumSignatures: 1,
PublicKeys: []crypto.PublicKey{pk},
PublicKeys: []consensus.SiaPublicKey{
consensus.SiaPublicKey{
Algorithm: consensus.ED25519Identifier,
},
},
}
copy(spendConditions.PublicKeys[0].Key, pk[:])
coinAddress = spendConditions.CoinAddress()
// Add the address to the set of spendable addresses.
......
......@@ -209,16 +209,19 @@ func (w *Wallet) SignTransaction(id string, wholeTransaction bool) (txn consensu
}
txn.Signatures = append(txn.Signatures, sig)
// Hash the transaction according to the covered fields and produce the
// cryptographic signature.
// Hash the transaction according to the covered fields.
coinAddress := input.SpendConditions.CoinAddress()
sigIndex := len(txn.Signatures) - 1
secKey := w.keys[coinAddress].secretKey
sigHash := txn.SigHash(sigIndex)
txn.Signatures[sigIndex].Signature, err = crypto.SignBytes(sigHash[:], secKey)
// Get the signature.
var encodedSig crypto.Signature
encodedSig, err = crypto.SignBytes(sigHash[:], secKey)
if err != nil {
return
}
copy(txn.Signatures[sigIndex].Signature, encodedSig[:])
}
// Delete the open transaction.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment