Arquitecturas de las wallets
Oradores: Andrew Chow
Fecha: June 5, 2019
Transcripción De: Bryan Bishop
Traducción Por: Blue Moon
Tags: Wallet, Bitcoin core, Descriptors
Arquitectura de la wallet de Bitcoin Core + descriptores
https://twitter.com/kanzure/status/1136282460675878915
writeup: https://github.com/bitcoin/bitcoin/issues/16165
Debate sobre la arquitectura de las wallets
Aquí hay tres áreas principales. Una es IsMine: ¿cómo determino que una salida concreta está afectando a mi wallet? ¿Qué hay de pedir una nueva dirección, de dónde viene? Eso no es sólo obtener una nueva dirección, es obtener una dirección de cambio en bruto, es también el cambio que se crea en fundrawtransaction. El tercer problema es la firma de la cartera. El almacenamiento no es un punto de entrada, es sólo una forma de implementar estas cosas.
Ahora mismo IsMine es independiente de la cartera, por alguna razón. No es parte de CWallet. Está bien abstraerlo. IsMine es relevante cuando entra una nueva transacción y queremos ver nuestro saldo. Se usa para las funciones getcredit/getdebit de las salidas de la wallet, se usa en muchos sitios. Cuando una transacción entra, la billetera es notificada.
Arquitectura de wallets heredados en la actualidad
El almacén de claves de la wallet contiene las claves privadas, scripts, cosas vigiladas. IsMine recibe consultas, y luego él mismo consulta el almacén de claves. Así es como funcionan las cosas ahora. Luego está el almacén de claves que tiene la información de la clave HD, y realmente esto es algo que se consulta de vez en cuando, se le pide que rellene el almacén de claves, y el almacén de claves pone las cosas en esa masa de datos. La única entrada de IsMine es scriptpubkey. Luego hay varias sobrecargas, una donde puedes darle un txout, entonces busca el txout y luego pasa el script a las comprobaciones de IsMine. El código de firma también llama al almacén de claves d la wallet. El código de firma utiliza un proveedor de firma, pero el proveedor de firma es implementado por el almacén de claves. Es esta cosa donde todo es volcado y consultado. Pero es de muy bajo nivel. No entiende lo que está pasando. Importas un script, importas algunas claves, y resulta que sabe que puede firmar por ello, así que vamos a llamarlo IsMine. No tiene ni idea de lo que las cosas están destinadas a ser … también, el keypool tiene claves, pero no tiene direcciones, por lo que no puede razonar sobre qué tipo de dirección que le gustaría tener.
Un caso de uso del que hemos hablado antes es que quieras obtener una dirección segwit, y no tenemos forma de importar una dirección bech32 sin importar también la correspondiente versión p2sh envuelta de la misma, y también el legado p2pkh, y p2pk que ni siquiera tiene una dirección. Así que introduce descriptores.
Registros de descriptores y gestores de scriptpubkey
Wallets descriptoras nativas https://github.com/bitcoin/bitcoin/pull/15764
Tienes una transacción que tiene un script. Es un IsMine en el descriptor. No, no necesitas eso. Cada descriptor tiene su propia clave, algo así. El tipo de cosa central es algo como esto, yo los llamaría “registros”. Estos registros contienen un descriptor, pero también contienen cada registro tiene su propio - no un grupo de claves, sino una caché de expansión. Tiene cosas como lookahead, información de los límites de la brecha, hasta qué punto se ha explorado que es implícita debido a la caché tal vez no. Tienes múltiples registros. Algunos de estos registros se marcan como “este es el que se obtiene bech32 direcciones de”. El keypool ya no está, esencialmente, sólo es parte del registro. El registro se identifica por un hash del descriptor. Tú configuras que la “fuente bech32 por defecto” sea “este” registro u otro. Con estos registros, puedes poblar un conjunto de scriptpubkeys para buscar. Nosotros solo las consultamos. La función es “expandir” creo. Tiras los metadatos como los scripts y salen las claves, y luego recuerdas las scriptpubkeys y las recuerdas en un conjunto. Ahora “IsMine” es “existe en este set”. Eso es lo único que necesitas hacer. Esto es consistente con los pull requests hasta ahora. Esto es consistente con el PR de la wallet descriptora.
¿Cómo se ocupa ese PR del conjunto de claves? En realidad, ya no tenemos el concepto de reserva de claves. Hay algo en el registro que es el índice de inicio y fin. Si dices que quieres un bech32, va al inicio actual y lo obtiene y luego lo devuelve. Pero no usa get key o lo que sea. He sustituido que para las carteras descriptor para ser “obtener la dirección de la” .. y te da la dirección, no la clave. Idealmente, convertiríamos lo viejo para lidiar con esta dirección de brecha.
Creo que queremos una abstracción en torno a keypool, almacén de claves, y IsMine. Aquí hay una dirección que he visto, ¿la escritura es mía? Deberíamos tener una implementación para que que utiliza keystore y toda la lógica existente. Entonces podemos crear una nueva que es el sistema de registros, y tendría exactamente el mismo punto de entrada. Le preguntas a los descriptores, ¿esto es mío? Todo lo que hace es probar si está en un conjunto. Pero si le preguntas al otro, va a través de la vieja lógica. Para los registros, hay un punto de entrada como “He visto esta secuencia de comandos en la red o en la cartera para avanzar el …”, pero el viejo sistema avanza el keypool, pero los registros de uno aumenta el lookahead puntos.
Firmar implica expandir la clave privada. El problema de expandir claves privadas es, ¿cómo sabes que este scriptpubkey pertenece a este descriptor? Tengo que expandir a algún índice también. Asi que lo que habia hecho, parte de meterlo en lo existente, cuando haces “top up keypool”, expande todas las claves privadas y las escribe como claves normales. ¿Podemos no hacer eso? La otra forma de hacerlo era realmente tonta. Tenía que tener un mapa de todas las scriptpubkeys y están mapeando a….. sí. Necesitas un mapa de scriptpubkeys o incluso hashes de scriptpubkeys a que descriptor y que posición en el. Eso es útil de todos modos, porque si estoy haciendo getaddressinfo, debemos reportar esa información en getaddressinfo. Realmente me gustaria que ya no escribamos cada clave que usamos, en el archivo de la wallet. Eso es exactamente lo que ya no debería ser necesario. Con el caché de expansión, es rápido. Las generas sobre la marcha, actualizas sobre la marcha, la aumentas, se empujan al mapa. Las claves privadas son casi completamente independientes. Creo que las claves privadas para los registros, ni siquiera son parte de los registros, sólo “aquí están las claves privadas a las que tengo acceso” en el sentido de que lo ves como un dispositivo de firma de software. Cuando intentas firmar, utilizas ese conjunto de claves privadas para ayudarte con la derivación. Creo que no deberíamos ver las claves privadas como parte de la wallet, son más… hay un dispositivo de firma de software que tienes incorporado en tu wallet, pero potencialmente tienes la wallet de hardware que tienen otras claves privadas. Realmente no debería importar donde vive la clave privada, hasta que tengas que firmar. Si has reforzado la derivación, entonces también necesitas acceder a la clave privada en el momento de la derivación. Pero incluso eso no debería depender de si tienes tus claves privadas localmente o no. Debería funcionar uniformemente si está en otro lugar.
La antigua firma necesitaba el almacén de claves porque el proveedor de firma no proporcionaba ciertas cosas. Era exasperante.
La wallet tiene un montón de objetos de gestión scriptpubkey. Tiene estas interfaces de ISMine, firmar, y aquí hay algo que vi y dame una nueva dirección. Cosas que son cosas actuales de la cartera se mueven a estos objetos de gestión scriptpubkey. Ahora mismo no hay una forma híbrida entre el viejo IsMine y estas cosas nuevas. Esto ni siquiera debería ser wallet.cpp. No quieres duplicar una cartera entera sólo porque cambia la gestión de scriptpubkey. Quieres abstraer la gestión de scriptpubkey. ¿Es eso un prerrequisito? Así que quieres convertir esto en una nueva caja, y escribir otras cajas que tengan la misma interfaz y la misma forma. Puedes tener una clase virtual scriptpubkey management, y una implementación legacy scriptpubkey management, y una implementación descriptor scriptpubkey management y guardas cualquiera de estas en la cartera. Así que este antiguo código heredado se trasladaría a un nuevo objeto de gestión scriptpubkey. CWallet debería permanecer como está; es una cosa de nivel superior, porque también gestiona cuentas, etiquetas, gestión de conjuntos UTXO. Nada de lo que hemos hablado toca la gestión UTXO o todas las diversas formas de calcular un saldo, o todas las cosas con coin selectoin que ya es un archivo separado afortunadamente. Todas esas cosas deben permanecer. Sólo estos puntos de entrada que tenemos que estandarizar. Getnewaddress va a través de un keypool y IsMine va a través de otra cosa. Es molesto. Estas cosas no deben ir a diferentes cosas, que son proporcionados por una cosa.
Nuestro método heredado tiene un método getnewaddress, un método ismine que delega a ismine.cpp inicialmente por ahora, también tiene “vi un scriptpubkey”. Las llamadas RPC como getnewaddress necesitan un nuevo argumento como ¿con qué saco de llaves quieres hablar? No, eso es sólo multiwallet. CWallet no debería tener más de un objeto de gestión scriptpubkey. Múltiples descriptores deberían formar parte de un objeto de gestión scriptpubkey. No, cada registro debería ser un objeto de gestión scriptpubkey. Elección de implementación. En el futuro, probablemente quieras apuntar a un descriptor en particular cuando llames a getnewaddress. No estoy seguro de eso. ¿De qué objeto de gestión scriptpubkey obtienes ese tipo de dirección? Podría ser uno heredado u otro. Me parece bien decir que no se puede mezclar legacy con no-legacy. Pero aparte de la complejidad de las pruebas y la complejidad del usuario … las pruebas en gran medida no debe cambiar.
Así que tenemos una bolsa de registros, esa es la unidad de la que estamos hablando. ¿Esa bolsa tiene un método getnewaddress, y depende del registro que pueda hacer eso? Eso sería simplemente CWallet. Sabría a qué objeto de gestión scriptpubkey pedir qué tipo de dirección. Si le pides a la cartera una dirección bech32, la cartera la reenviaría al gestor scriptpubkey correcto. En realidad es más un gestor de scriptpubkey, no un objeto de gestión de claves, así que dejemos de llamarlo así. Ese es en realidad el punto. Es “algo interno de la wallet”. No, no es sólo una cartera.
Cuando dices “gestor de claves”, pienso en claves privadas. Bien, llamémoslo gestor de SPK o gestor de scriptpubkey. La cartera contiene múltiples objetos scriptpubkey manager.
La razón por la que el objeto CKeyStore fue creado hace mucho tiempo fue por una dependencia circular entre la lógica de firma de script que estaba en script.cpp y la cartera en wallet.cpp. El código del script necesitaba acceso a las claves privadas que necesitaba la wallet, pero claramente la wallet necesitaba poder llamar al código del script. Así que una interfaz llamada CKeyStore fue abstraída para acceder a la clave privada. La wallet tenía una implementación de este almacén de claves, pero desafortunadamente esa implementación terminó en keystore.cpp en lugar de wallet.cpp. Siempre debería haber sido, usted tiene un archivo externo con la interfaz, y luego la cartera tiene una implementación para ello. Con el tiempo, cada vez que la gente quería añadir cosas a lo que la cartera estaba almacenando, se añadió a keystore.cpp y todas esas cosas deberían haber permanecido en la cartera todo el tiempo. Para arreglar eso, creamos el proveedor de firma, que es realmente la interfaz que keystore fue diseñado para resolver, y ahora keystore implementa un proveedor de firma, y creo que deberíamos deshacernos de keystore. Todavía existe esta bolsa de objetos, cierto, pero eso es algo específico de la.
CWallet es su propia clase que no hereda nada. Podríamos tener un método como “firmar esta transacción”, en lugar de exponer… No sé lo que es más fácil de hacer eso. Averiguar lo que los, límites de lo que esta caja debe implicar exactamente. Hay un montón de lógica para varios tipos de casos como cuando se obtiene una nueva clave pública para la que conocemos la clave privada, entonces añadimos el p2sh para el que conocemos el p2wpkh, y que también debería estar en la walle tbox para que no lo necesitemos en esta otra caja.
Serialización
Con respecto a la serialización… está estrechamente vinculada a CWallet y a la base de datos. Cuando cargas una wallet, empieza a instanciar objetos inmediatamente. La serialización sólo puede ser hecha por las partes de CWallet. Usted pasaría el objeto de base de datos. Hay un montón de nuevos objetos serializados que necesitan t ogo en estos, pero no todos estos. Estás leyendo objeto por objeto en la base de datos, y estás instnatiating nuevos objetos y tienen que ir en esta colección. La mayoría de estas cosas pertenecerán a una caja, por lo que diría, la bandera dice que esta es una cartera de legado, como deserializar, se ve una clave de semilla, lo pasa a la caja. Así como deserializar, se crea la caja y que acaba de empezar a tirar las cosas en ella, y si ves una cartera descriptor a continuación, empezar a tirar las cosas en esa caja. La caja descriptor es- son- tienen que ser un nuevo registro.
De vuelta a otras cosas de la wallet..
Hay bastantes pruebas que dependen del tonto comportamiento “IsMine”. Las pruebas funcionales. La totalidad de la cartera, básicamente. Hay un montón de cosas compartidas entre estos.
Etiquetas y libreta de direcciones, ¿cómo se interconectan ahora? Tenemos un registro que toma la dirección como etiqueta. Es independiente de añadir un script o lo que sea al almacén de claves. Sí, son diferentes. IsMine en el nuevo, no tiene tal cosa como “InMine solvable”. Es o spendable, o watchonly. Si tienes una wallet de hardware, queremos llamarla “gastable”. Para watchonly, tal vez usar multiwallet.