db/ticket: Add AltSigAddress.
To allow signing with addresses other than the commitment address, add an alternate signature address. In order to continue to prove that the address was chosen by the user, add an alternate signature history. Only allow one record per ticket to be saved to cap needed db space.
This commit is contained in:
parent
623bb192d1
commit
6e13d23214
124
database/altsig.go
Normal file
124
database/altsig.go
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// The keys used to store the altsig in the database.
|
||||
var (
|
||||
altSigAddrK = []byte("altsig")
|
||||
reqK = []byte("req")
|
||||
reqSigK = []byte("reqsig")
|
||||
resK = []byte("res")
|
||||
resSigK = []byte("ressig")
|
||||
)
|
||||
|
||||
// AltSigData holds the information needed to prove that a client added an
|
||||
// alternate signature address.
|
||||
type AltSigData struct {
|
||||
// AltSigAddr is the new alternate signature address. It is base 58
|
||||
// encoded.
|
||||
AltSigAddr string
|
||||
// Req is the original request to set an alternate signature.
|
||||
Req []byte
|
||||
// ReqSig is the request's signature signed by the private key that
|
||||
// corresponds to the address. It is base 64 encoded.
|
||||
ReqSig string
|
||||
// Res is the original response from the server to the alternate
|
||||
// signature address.
|
||||
Res []byte
|
||||
// ResSig is the response's signature signed by the server. It is base
|
||||
// 64 encoded.
|
||||
ResSig string
|
||||
}
|
||||
|
||||
// InsertAltSig will insert the provided ticket into the database. Returns an
|
||||
// error if data for the ticket hash already exist.
|
||||
//
|
||||
// Passed data must have no empty fields.
|
||||
func (vdb *VspDatabase) InsertAltSig(ticketHash string, data *AltSigData) error {
|
||||
if data == nil {
|
||||
return errors.New("alt sig data must not be nil for inserts")
|
||||
}
|
||||
|
||||
if data.AltSigAddr == "" || len(data.Req) == 0 || data.ReqSig == "" ||
|
||||
len(data.Res) == 0 || data.ResSig == "" {
|
||||
return errors.New("alt sig data has empty parameters")
|
||||
}
|
||||
|
||||
return vdb.db.Update(func(tx *bolt.Tx) error {
|
||||
altSigBkt := tx.Bucket(vspBktK).Bucket(altSigBktK)
|
||||
|
||||
// Create a bucket for the new altsig. Returns an error if bucket
|
||||
// already exists.
|
||||
bkt, err := altSigBkt.CreateBucket([]byte(ticketHash))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create bucket for altsig: %w", err)
|
||||
}
|
||||
|
||||
if err := bkt.Put(altSigAddrK, []byte(data.AltSigAddr)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bkt.Put(reqK, data.Req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bkt.Put(reqSigK, []byte(data.ReqSig)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bkt.Put(resK, data.Res); err != nil {
|
||||
return err
|
||||
}
|
||||
return bkt.Put(resSigK, []byte(data.ResSig))
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAltSig deletes an altsig from the database. Does not error if there is
|
||||
// no altsig in the database.
|
||||
func (vdb *VspDatabase) DeleteAltSig(ticketHash string) error {
|
||||
return vdb.db.Update(func(tx *bolt.Tx) error {
|
||||
altSigBkt := tx.Bucket(vspBktK).Bucket(altSigBktK)
|
||||
|
||||
// Don't attempt delete if doesn't exist.
|
||||
bkt := altSigBkt.Bucket([]byte(ticketHash))
|
||||
if bkt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := altSigBkt.DeleteBucket([]byte(ticketHash))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not delete altsig: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// AltSigData retrieves a ticket's alternate signature data. Existence of an
|
||||
// alternate signature can be inferred by no error and nil data return.
|
||||
func (vdb *VspDatabase) AltSigData(ticketHash string) (*AltSigData, error) {
|
||||
var h *AltSigData
|
||||
return h, vdb.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(vspBktK).Bucket(altSigBktK).Bucket([]byte(ticketHash))
|
||||
if bkt == nil {
|
||||
return nil
|
||||
}
|
||||
h = &AltSigData{
|
||||
AltSigAddr: string(bkt.Get(altSigAddrK)),
|
||||
Req: bkt.Get(reqK),
|
||||
ReqSig: string(bkt.Get(reqSigK)),
|
||||
Res: bkt.Get(resK),
|
||||
ResSig: string(bkt.Get(resSigK)),
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
117
database/altsig_test.go
Normal file
117
database/altsig_test.go
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func exampleAltSigData() *AltSigData {
|
||||
return &AltSigData{
|
||||
AltSigAddr: randString(35, addrCharset),
|
||||
Req: randBytes(1000),
|
||||
ReqSig: randString(96, sigCharset),
|
||||
Res: randBytes(1000),
|
||||
ResSig: randString(96, sigCharset),
|
||||
}
|
||||
}
|
||||
|
||||
func ensureData(t *testing.T, ticketHash string, wantData *AltSigData) {
|
||||
t.Helper()
|
||||
|
||||
data, err := db.AltSigData(ticketHash)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error fetching alt signature data: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(wantData, data) {
|
||||
t.Fatal("want data different than actual")
|
||||
}
|
||||
}
|
||||
|
||||
func testAltSigData(t *testing.T) {
|
||||
ticketHash := randString(64, hexCharset)
|
||||
|
||||
// Not added yet so no values should exist in the db.
|
||||
h, err := db.AltSigData(ticketHash)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error fetching alt signature data: %v", err)
|
||||
}
|
||||
if h != nil {
|
||||
t.Fatal("expected no data")
|
||||
}
|
||||
|
||||
// Insert an altsig.
|
||||
data := exampleAltSigData()
|
||||
if err := db.InsertAltSig(ticketHash, data); err != nil {
|
||||
t.Fatalf("unexpected error storing altsig in database: %v", err)
|
||||
}
|
||||
|
||||
ensureData(t, ticketHash, data)
|
||||
}
|
||||
|
||||
func testInsertAltSig(t *testing.T) {
|
||||
ticketHash := randString(64, hexCharset)
|
||||
|
||||
// Not added yet so no values should exist in the db.
|
||||
ensureData(t, ticketHash, nil)
|
||||
|
||||
data := exampleAltSigData()
|
||||
// Clear alt sig addr for test.
|
||||
data.AltSigAddr = ""
|
||||
|
||||
if err := db.InsertAltSig(ticketHash, data); err == nil {
|
||||
t.Fatalf("expected error for insert blank address")
|
||||
}
|
||||
|
||||
if err := db.InsertAltSig(ticketHash, nil); err == nil {
|
||||
t.Fatalf("expected error for nil data")
|
||||
}
|
||||
|
||||
// Still no change on errors.
|
||||
ensureData(t, ticketHash, nil)
|
||||
|
||||
// Re-add alt sig addr.
|
||||
data.AltSigAddr = randString(35, addrCharset)
|
||||
|
||||
// Insert an altsig.
|
||||
if err := db.InsertAltSig(ticketHash, data); err != nil {
|
||||
t.Fatalf("unexpected error storing altsig in database: %v", err)
|
||||
}
|
||||
|
||||
ensureData(t, ticketHash, data)
|
||||
|
||||
// Further additions should error and not change the data.
|
||||
secondData := exampleAltSigData()
|
||||
secondData.AltSigAddr = data.AltSigAddr
|
||||
if err := db.InsertAltSig(ticketHash, secondData); err == nil {
|
||||
t.Fatalf("expected error for second altsig addition")
|
||||
}
|
||||
|
||||
ensureData(t, ticketHash, data)
|
||||
}
|
||||
|
||||
func testDeleteAltSig(t *testing.T) {
|
||||
ticketHash := randString(64, hexCharset)
|
||||
|
||||
// Nothing to delete.
|
||||
if err := db.DeleteAltSig(ticketHash); err != nil {
|
||||
t.Fatalf("unexpected error deleting nonexistant altsig")
|
||||
}
|
||||
|
||||
// Insert an altsig.
|
||||
data := exampleAltSigData()
|
||||
if err := db.InsertAltSig(ticketHash, data); err != nil {
|
||||
t.Fatalf("unexpected error storing altsig in database: %v", err)
|
||||
}
|
||||
|
||||
ensureData(t, ticketHash, data)
|
||||
|
||||
if err := db.DeleteAltSig(ticketHash); err != nil {
|
||||
t.Fatalf("unexpected error deleting altsig: %v", err)
|
||||
}
|
||||
|
||||
ensureData(t, ticketHash, nil)
|
||||
}
|
||||
@ -50,6 +50,8 @@ var (
|
||||
privateKeyK = []byte("privatekey")
|
||||
// lastaddressindex is the index of the last address used for fees.
|
||||
lastAddressIndexK = []byte("lastaddressindex")
|
||||
// altSigBktK stores alternate signatures.
|
||||
altSigBktK = []byte("altsigbkt")
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@ -22,12 +23,36 @@ const (
|
||||
backupDb = "test.db-backup"
|
||||
feeXPub = "feexpub"
|
||||
maxVoteChangeRecords = 3
|
||||
|
||||
addrCharset = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
hexCharset = "1234567890abcdef"
|
||||
sigCharset = "0123456789ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/="
|
||||
)
|
||||
|
||||
var (
|
||||
db *VspDatabase
|
||||
db *VspDatabase
|
||||
seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
)
|
||||
|
||||
// randBytes returns a byte slice of size n filled with random bytes.
|
||||
func randBytes(n int) []byte {
|
||||
slice := make([]byte, n)
|
||||
if _, err := seededRand.Read(slice); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// randString randomly generates a string of the requested length, using only
|
||||
// characters from the provided charset.
|
||||
func randString(length int, charset string) string {
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[seededRand.Intn(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// TestDatabase runs all database tests.
|
||||
func TestDatabase(t *testing.T) {
|
||||
// Ensure we are starting with a clean environment.
|
||||
@ -47,6 +72,9 @@ func TestDatabase(t *testing.T) {
|
||||
"testDeleteTicket": testDeleteTicket,
|
||||
"testVoteChangeRecords": testVoteChangeRecords,
|
||||
"testHTTPBackup": testHTTPBackup,
|
||||
"testAltSigData": testAltSigData,
|
||||
"testInsertAltSig": testInsertAltSig,
|
||||
"testDeleteAltSig": testDeleteAltSig,
|
||||
}
|
||||
|
||||
for testName, test := range tests {
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@ -13,22 +12,7 @@ import (
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
// randString randomly generates a string of the requested length, using only
|
||||
// characters from the provided charset.
|
||||
func randString(length int, charset string) string {
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[seededRand.Intn(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func exampleTicket() Ticket {
|
||||
const hexCharset = "1234567890abcdef"
|
||||
const addrCharset = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
|
||||
return Ticket{
|
||||
Hash: randString(64, hexCharset),
|
||||
CommitmentAddress: randString(35, addrCharset),
|
||||
|
||||
41
database/upgrade_v4.go
Normal file
41
database/upgrade_v4.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
func altSigUpgrade(db *bolt.DB) error {
|
||||
log.Infof("Upgrading database to version %d", altSigVersion)
|
||||
|
||||
// Run the upgrade in a single database transaction so it can be safely
|
||||
// rolled back if an error is encountered.
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
vspBkt := tx.Bucket(vspBktK)
|
||||
|
||||
// Create altsig bucket.
|
||||
_, err := vspBkt.CreateBucket(altSigBktK)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create %s bucket: %w", altSigBktK, err)
|
||||
}
|
||||
|
||||
// Update database version.
|
||||
err = vspBkt.Put(versionK, uint32ToBytes(altSigVersion))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update db version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Upgrade completed")
|
||||
return nil
|
||||
}
|
||||
@ -25,10 +25,14 @@ const (
|
||||
// moves each ticket into its own bucket and does away with JSON encoding.
|
||||
ticketBucketVersion = 3
|
||||
|
||||
// altSigVersion adds a bucket to store alternate signatures used to verify
|
||||
// messages sent to the vspd.
|
||||
altSigVersion = 4
|
||||
|
||||
// latestVersion is the latest version of the database that is understood by
|
||||
// vspd. Databases with recorded versions higher than this will fail to open
|
||||
// (meaning any upgrades prevent reverting to older software).
|
||||
latestVersion = ticketBucketVersion
|
||||
latestVersion = altSigVersion
|
||||
)
|
||||
|
||||
// upgrades maps between old database versions and the upgrade function to
|
||||
@ -36,6 +40,7 @@ const (
|
||||
var upgrades = []func(tx *bolt.DB) error{
|
||||
initialVersion: removeOldFeeTxUpgrade,
|
||||
removeOldFeeTxVersion: ticketBucketUpgrade,
|
||||
ticketBucketVersion: altSigUpgrade,
|
||||
}
|
||||
|
||||
// v1Ticket has the json tags required to unmarshal tickets stored in the
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user