diff --git a/waddrmgr/db.go b/waddrmgr/db.go index 9ad1a59..12612de 100644 --- a/waddrmgr/db.go +++ b/waddrmgr/db.go @@ -70,9 +70,10 @@ type addressType uint8 // These constants define the various supported address types. const ( - adtChain addressType = 0 - adtImport addressType = 1 // not iota as they need to be stable for db - adtScript addressType = 2 + adtChain addressType = 0 + adtImport addressType = 1 // not iota as they need to be stable for db + adtScript addressType = 2 + adtWitnessScript addressType = 3 ) // accountType represents a type of address stored in the database. @@ -155,6 +156,27 @@ type dbScriptAddressRow struct { encryptedScript []byte } +// dbWitnessScriptAddressRow houses additional information stored about a +// witness script address in the database. +type dbWitnessScriptAddressRow struct { + dbAddressRow + + // witnessVersion is the version of the witness script. + witnessVersion byte + + // isSecretScript denotes whether the script is considered to be + // "secret" and encrypted with the script encryption key or "public" and + // therefore only encrypted with the public encryption key. + isSecretScript bool + encryptedHash []byte + + // encryptedScript is the actual payload of the script address and + // represents the script itself. The encoding of the script is up to the + // actual implementation, it is not parsed or interpreted in any way by + // the database code. So it can be a plain script or a TLV encoded MAST. + encryptedScript []byte +} + // Key names for various database fields. var ( // nullVall is null byte used as a flag value in a bucket entry @@ -1497,6 +1519,74 @@ func serializeScriptAddress(encryptedHash, encryptedScript []byte) []byte { return rawData } +// deserializeWitnessScriptAddress deserializes the raw data from the passed +// address row as a witness script address. +func deserializeWitnessScriptAddress( + row *dbAddressRow) (*dbWitnessScriptAddressRow, error) { + + // The serialized witness script address raw data format is: + // + // + // + // 1 byte witness version + 1 byte boolean + 4 bytes encrypted script + // hash len + encrypted script hash + 4 bytes encrypted script len + + // encrypted script + const minLength = 1 + 1 + 4 + 4 + + // Given the above, the length of the entry must be at a minimum + // the constant value sizes. + if len(row.rawData) < minLength { + str := "malformed serialized witness script address" + return nil, managerError(ErrDatabase, str, nil) + } + + retRow := dbWitnessScriptAddressRow{ + dbAddressRow: *row, + witnessVersion: row.rawData[0], + isSecretScript: row.rawData[1] == 1, + } + + hashLen := binary.LittleEndian.Uint32(row.rawData[2:6]) + retRow.encryptedHash = make([]byte, hashLen) + copy(retRow.encryptedHash, row.rawData[6:6+hashLen]) + offset := 6 + hashLen + scriptLen := binary.LittleEndian.Uint32(row.rawData[offset : offset+4]) + offset += 4 + retRow.encryptedScript = make([]byte, scriptLen) + copy(retRow.encryptedScript, row.rawData[offset:offset+scriptLen]) + + return &retRow, nil +} + +// serializeWitnessScriptAddress returns the serialization of the raw data field +// for a witness script address. +func serializeWitnessScriptAddress(witnessVersion uint8, isSecretScript bool, + encryptedHash, encryptedScript []byte) []byte { + + // The serialized witness script address raw data format is: + // + // + // + // 1 byte witness version + 1 byte boolean + 4 bytes encrypted script + // hash len + encrypted script hash + 4 bytes encrypted script len + + // encrypted script + + hashLen := uint32(len(encryptedHash)) + scriptLen := uint32(len(encryptedScript)) + rawData := make([]byte, 10+hashLen+scriptLen) + rawData[0] = witnessVersion + if isSecretScript { + rawData[1] = 1 + } + binary.LittleEndian.PutUint32(rawData[2:6], hashLen) + copy(rawData[6:6+hashLen], encryptedHash) + offset := 6 + hashLen + binary.LittleEndian.PutUint32(rawData[offset:offset+4], scriptLen) + offset += 4 + copy(rawData[offset:offset+scriptLen], encryptedScript) + return rawData +} + // fetchAddressByHash loads address information for the provided address hash // from the database. The returned value is one of the address rows for the // specific address type. The caller should use type assertions to ascertain @@ -1530,6 +1620,8 @@ func fetchAddressByHash(ns walletdb.ReadBucket, scope *KeyScope, return deserializeImportedAddress(row) case adtScript: return deserializeScriptAddress(row) + case adtWitnessScript: + return deserializeWitnessScriptAddress(row) } str := fmt.Sprintf("unsupported address type '%d'", row.addrType) @@ -1747,6 +1839,30 @@ func putScriptAddress(ns walletdb.ReadWriteBucket, scope *KeyScope, return nil } +// putWitnessScriptAddress stores the provided witness script address +// information to the database. +func putWitnessScriptAddress(ns walletdb.ReadWriteBucket, scope *KeyScope, + addressID []byte, account uint32, status syncStatus, + witnessVersion uint8, isSecretScript bool, encryptedHash, + encryptedScript []byte) error { + + rawData := serializeWitnessScriptAddress( + witnessVersion, isSecretScript, encryptedHash, encryptedScript, + ) + addrRow := dbAddressRow{ + addrType: adtWitnessScript, + account: account, + addTime: uint64(time.Now().Unix()), + syncStatus: status, + rawData: rawData, + } + if err := putAddress(ns, scope, addressID, &addrRow); err != nil { + return err + } + + return nil +} + // existsAddress returns whether or not the address id exists in the database. func existsAddress(ns walletdb.ReadBucket, scope *KeyScope, addressID []byte) bool { scopedBucket, err := fetchReadScopeBucket(ns, scope) @@ -2000,6 +2116,30 @@ func deletePrivateKeys(ns walletdb.ReadWriteBucket) error { str := "failed to delete imported script" return managerError(ErrDatabase, str, err) } + + case adtWitnessScript: + srow, err := deserializeWitnessScriptAddress(row) + if err != nil { + return err + } + + // If the script is considered to be public, we + // don't need to do anything. + if !srow.isSecretScript { + return nil + } + + // Re-serialize the script address without the + // script and store it. + row.rawData = serializeWitnessScriptAddress( + srow.witnessVersion, srow.isSecretScript, + srow.encryptedHash, nil, + ) + err = bucket.Put(k, serializeAddressRow(row)) + if err != nil { + str := "failed to delete imported script" + return managerError(ErrDatabase, str, err) + } } return nil