Extensi贸n NodeGuard Signer: C贸mo firmar un PSBT en Rust

Nota: La IA no ha escrito este art铆culo. Esto es precisamente lo que dir铆a una IA, 驴no? Sospechoso 馃…
Rodrigo
Rodrigo S谩nchez
Software Developer
Nodes connected

En Clovr Labs, desarrollamos NodeGuard, un sistema de gesti贸n de tesorer铆a que permite asegurar dinero detr谩s de billeteras con m煤ltiples firmas (entre otras caracter铆sticas). Uno de los beneficios de las billeteras con m煤ltiples firmas es que, debido a su construcci贸n, un 鈥渃ontrato鈥 impide que una sola persona posea los fondos. Para que el dinero cambie de manos, m煤ltiples personas deben firmar la operaci贸n para aprobar el cambio de propiedad.

Para facilitar la firma, estamos desarrollando una extensi贸n de navegador que permite a las personas importar sus semillas y firmar una operaci贸n. Esta extensi贸n ser谩 de c贸digo abierto en el futuro. Estar谩 disponible para que todos la usen, no solo en la aplicaci贸n NodeGuard, sino tambi茅n en sus propias aplicaciones.

Ahora, hablemos de las billeteras de m煤ltiples firmas, tambi茅n conocidas como billeteras multi-sig. Como se mencion贸 antes, las billeteras multi-sig son billeteras en las que la propiedad de los fondos pertenece a m谩s de una persona a la vez. Adem谩s, las billeteras multi-sig se describen mediante el t茅rmino 鈥淢-de-N鈥, donde 鈥淣鈥 denota el n煤mero total de firmantes que 鈥渁cuerdan鈥 construir la billetera, mientras que 鈥淢鈥 representa el n煤mero de firmantes necesarios para realizar una operaci贸n o 鈥渢ransacci贸n鈥 con estos fondos.

Pongamos un ejemplo. Imagina que pertenezco a un grupo de 3 amigos y juntos construimos una billetera multi-sig de 2-de-3. Eso significa que los 3 鈥渁cordamos鈥 crear una billetera juntos, y solo necesitar铆amos la firma de 2 amigos para cambiar la propiedad de los fondos. Como queremos enviar fondos a otra billetera, otro amigo y yo debemos firmar la transacci贸n. Aunque mi otro amigo 鈥渁cord贸鈥 ser parte de la billetera, no es necesario que firme.

Hagamos otro ejemplo. Tengo un amigo que tiene un negocio con otro socio. Mi amigo viene del sector de la construcci贸n y su socio del sector de la arquitectura; no conf铆an el uno en el otro, pero decidieron emprender juntos porque se complementan bien. Decidieron crear una billetera Bitcoin multi-sig de 2-de-2. En este escenario, ambos acordaron crear la billetera. El contrato especifica que se necesitan ambas firmas para transaccionar con sus fondos.

Como puedes ver, la flexibilidad de las billeteras M-de-N permite un mundo de posibilidades en las que los fondos est谩n asegurados mediante muchas configuraciones diferentes: 3-de-6, 2-de-2, etc.

馃敼 Wallet

Hablamos sobre las billeteras multi-sig, pero 驴c贸mo se construyen? Mencionamos en la 煤ltima parte que necesitamos firmar algo (hablaremos de qu茅 m谩s adelante) para realizar una transacci贸n. La forma en que funcionan las firmas en Bitcoin es muy similar a c贸mo funciona una caja fuerte en la vida real. Para acceder al contenido de una caja fuerte, necesitas demostrar que conoces la combinaci贸n. De manera comparativa, para demostrar que tienes la propiedad de fondos en Bitcoin, necesitas demostrar que posees una 芦clave privada禄, que es un n煤mero de 256 bits. Esta clave privada puede codificarse como 64 caracteres hexadecimales para que los humanos puedan leerla f谩cilmente. En el caso de una billetera multi-sig de 2-de-3, puedes imaginar una caja fuerte con 3 teclados, pero solo se necesitan dos de las 3 combinaciones para abrirla. As铆 que, en el caso de una billetera de Bitcoin, t煤 (o tus amigos) necesitan demostrar que conocen 2 de las claves privadas para firmar una transacci贸n.

馃敼 Claves p煤blicas

Una clave p煤blica es una parte esencial de c贸mo funcionan las billeteras. Es la cara de una billetera y c贸mo se construyen las billeteras. La clave p煤blica se deriva de la clave privada y genera una direcci贸n para recibir Bitcoin. La derivaci贸n de una clave p煤blica es unidireccional; incluso si alguien conoce tu clave p煤blica, no puede recuperar la clave privada a partir de ella. Las claves p煤blicas se combinan para generar una direcci贸n para recibir fondos en una billetera multi-sig. Una vez que los fondos se transfieren a esa direcci贸n, se pueden usar las claves privadas para demostrar la propiedad de esa clave p煤blica y 鈥渄esbloquear鈥 los fondos para gastarlos. Esta es una de las principales razones por las que, cuando alguien pierde las claves de su billetera, los fondos se pierden para siempre, ya que es imposible reconstruir la clave privada a partir de una clave p煤blica. Y las posibilidades de adivinar una clave privada, incluso con computadoras potentes, son astron贸micamente peque帽as.

馃敼 Ruta de derivaci贸n

Una billetera de Bitcoin utiliza una ruta de derivaci贸n para derivar m煤ltiples claves p煤blicas y, a su vez, crear infinitas direcciones de Bitcoin diferentes para la misma billetera. Puedes pensar en las rutas de derivaci贸n como otras cajas fuertes pertenecientes a la misma persona que se encuentran en diferentes edificios, ciudades y pa铆ses. Para desbloquear el dinero de una caja fuerte, primero necesitamos saber el pa铆s, la ciudad, el edificio, la habitaci贸n y el n煤mero de la caja fuerte. Con esa informaci贸n, sabemos en qu茅 caja fuerte introducir la contrase帽a, y la contrase帽a que usaremos depender谩 del n煤mero de la caja fuerte.

En Bitcoin, en cambio, las contrase帽as de las diferentes cajas fuertes en una habitaci贸n pueden derivarse de la contrase帽a que desbloquea la puerta de la habitaci贸n. Esto tambi茅n es seguro si piensas en una habitaci贸n como una gran caja fuerte con una puerta protegida por contrase帽a. No hablaremos aqu铆 de algunas otras complejidades respecto a las rutas de derivaci贸n. Pero, para firmar una transacci贸n, primero necesitamos saber en qu茅 芦caja fuerte禄 est谩 el dinero y si podemos demostrar la propiedad del secreto para usar ese dinero.

馃敼 Huella dactilar

Una huella digital es una versi贸n m谩s compacta de una clave privada. Como su nombre sugiere, identifica una clave privada sin utilizar la clave privada larga en s铆. En nuestro caso, la usaremos para verificar r谩pidamente que nuestra clave privada puede utilizarse para firmar una transacci贸n y demostrar que el dinero es nuestro antes de firmar la transacci贸n.

馃敼 Entradas

Las entradas consisten en informaci贸n que indica de d贸nde proviene el dinero, una referencia a las salidas de transacciones anteriores y la informaci贸n que demuestra que los fondos que queremos usar est谩n autorizados para ser gastados. Cada vez que realizas una transacci贸n, por ejemplo, gastar dinero en Bitcoin, se genera un id de transacci贸n como el resultado de esa transacci贸n. La entrada contiene el id de la transacci贸n anterior realizada, por lo que puedes rastrear el dinero hasta su creaci贸n. Debido a que una transacci贸n anterior puede hacerse con m谩s de una entrada, es necesario un 铆ndice para identificar qu茅 entradas de transacci贸n se est谩n gastando. Esto puede ser algo dif铆cil de seguir, as铆 que veamos la anatom铆a de una transacci贸n:
    
    Transaction: TXID: f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16
    Input 1: a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d (vout 0)
    Input 2: 4ce18f49ba153a51bcda9bb80d7f978e3de6e81b5fc326f00465464530c052f4 (vout 2)
    Output 1: 1 BTC to mvhMvZi4m17c8YR44YB64LisKYA3NbqBvS
    Output 2: 1 BTC to mgYJwtk2SWLcBbuNGnUV4dza1MERoHA9SR
    
  

Como puedes ver, se gener贸 un id de transacci贸n (TXID) para esta transacci贸n. En este caso, mi transacci贸n tiene dos salidas a dos billeteras diferentes, y estamos transfiriendo 1 BTC a cada billetera. Tambi茅n tenemos entradas 1 y 2; tienen el id de transacci贸n de la anterior y un 铆ndice llamado 鈥渧out鈥 que hace referencia a cu谩l de las salidas de la transacci贸n anterior se est谩 gastando. En este ejemplo, la transacci贸n est谩 gastando la Salida n煤mero 1 (vout 0) de la transacci贸n anterior a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d, y la Salida n煤mero 3 (vout 2) de la 煤ltima transacci贸n 4ce18f49ba153a51bcda9bb80d7f978e3de6e81b5fc326f00465464530c052f de la transacci贸n anterior 4. Y todo ese dinero gastado est谩 yendo a las direcciones mvhMvZi4m17c8YR44YB64LisKYA3NbqBvS y mgYJwtk2SWLcBbuNGnUV4dza1MERoHA9SR.

Como se mencion贸 antes, otra pieza vital de informaci贸n que contiene la entrada es un script que demuestra que los fondos que alguien est谩 a punto de gastar est谩n autorizados para ser gastados por esa persona. Esta prueba se realiza proporcionando una firma que coincide con este script. Como puedes ver, las firmas no son como las que haces en un trozo de papel, sino que son una forma de demostrar que eres el propietario de los fondos, y necesitas tener una clave privada para probar esa propiedad. Este script que contiene la entrada se llama 鈥淪cript de Desbloqueo鈥 porque proporciona la informaci贸n que necesitas verificar para desbloquear los fondos. Y para 鈥渁probar鈥 una transacci贸n, necesitas dar esta firma y agregarla a la estructura de datos de la entrada.

Vimos que las entradas hacen referencia a las salidas de transacciones anteriores, que contienen informaci贸n sobre c贸mo se puede gastar el dinero. El nombre de esta salida de transacci贸n no gastada es UTXO. Hablemos de ellas.

馃敼 UTXO

Una Salida de Transacci贸n No Gastada, UTXO por sus siglas en ingl茅s, es la salida de una transacci贸n anterior que a煤n no se ha gastado. Si recibiste dinero en tu billetera de Bitcoin y nunca lo gastaste, entonces eres el orgulloso propietario de UTXOs. Si gastas esos UTXOs, se utilizar谩n como entradas para una nueva transacci贸n.

Y lo m谩s importante para lo que necesitamos hacer, un UTXO contiene informaci贸n sobre las condiciones para gastar el dinero. Hay muchos 鈥渃ontratos鈥 en Bitcoin para gastar dinero. Puedes tener contratos basados en secuencias que definen que el dinero se puede gastar despu茅s de que se hayan minado varios bloques. Puedes tener contratos basados en el tiempo donde solo puedes gastar el dinero despu茅s de un tiempo espec铆fico. Puedes tener contratos 鈥減ersonalizados鈥 que permiten al propietario del dinero especificar c贸mo satisfacer el contrato para desbloquear los fondos. Estos 鈥渃ontratos鈥 se llaman 鈥淪cripts de Desbloqueo鈥. Para nuestros prop贸sitos, y porque necesitamos asegurarnos de que estamos reuniendo el n煤mero requerido de firmas, nos interesan los scripts de desbloqueo de m煤ltiples firmas 鈥淧ay to Script Hash鈥, uno de los contratos 鈥減ersonalizados鈥 que mencionamos anteriormente. En nuestro caso, el ejecutor de las condiciones de gasto es un script que contiene las claves p煤blicas de las personas que pueden firmar y el n煤mero de firmas requeridas para cumplir con la condici贸n de gasto. Un Pay-to-Script Hash puede verse as铆:

    
    2 PubKey1 PubKey2 PubKey3 3 CHECKMULTISIG
    
  

Veamos esto y veamos qu茅 podemos interpretar de ello. A primera vista, podemos ver CHECKMULTISIG escrito en el script. Este es el 鈥渢ipo鈥 del script, y le dice al c贸digo c贸mo interpretar el resto de los n煤meros y caracteres que aparecen en el resto del script. Los scripts de Bitcoin se leen de derecha a izquierda, por lo que CHECKMULTISIG es lo primero que lee el c贸digo. Gracias a CHECKMULTISIG, el c贸digo puede interpretar los n煤meros 2 y 3, que son los descriptores de la billetera permitidos para gastar el dinero. En este caso, estamos viendo una billetera multi-sig de 2-de-3. Necesitamos demostrar que 2 de las 3 claves p煤blicas que tambi茅n aparecen en este script pueden derivarse de nuestras claves privadas.

Como ves, entre las entradas y salidas, tenemos toda la informaci贸n necesaria para demostrar qui茅n es el propietario actual de los fondos (el script de bloqueo), que el propietario actual de los fondos permiti贸 la transferencia de fondos a una billetera diferente (por ejemplo, que gastaron el dinero), c贸mo demostrar que somos los nuevos propietarios del dinero despu茅s de que se realiza la transacci贸n (el script de desbloqueo), de d贸nde viene el dinero y a d贸nde va el dinero.

Una cosa que omit铆 sobre el script que estamos usando es que hay dos tipos de ellos: aquellos que tienen 鈥淪egregated Witness鈥 y aquellos que no. Dado que necesitaremos conocer la diferencia para aprobar esta transacci贸n, explicaremos estos dos tipos como nuestro pr贸ximo concepto.

馃敼 Segregated Witness

馃敼 Sighash

Un Sighash es un hash de los datos de la transacci贸n que 芦combinaremos禄 con la firma para firmar las entradas. Esto es 煤til para la verificaci贸n porque el hash no coincidir铆a con la firma si se modifican los datos dentro de una transacci贸n. Hay diferentes tipos de sighashes: SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE y SIGHASH_ANYONECANPAY. Todos describen qu茅 informaci贸n sobre las entradas y salidas se incluir谩 al crear el hash de la transacci贸n.

馃敼 PSBT

Finalmente, hemos llegado a la parte del art铆culo donde explicaremos d贸nde viven todos los conceptos que mencionamos anteriormente. Una de las formas en que se puede firmar una operaci贸n en una billetera multi-sig es a trav茅s de una Transacci贸n Parcialmente Firmada de Bitcoin (PSBT, por sus siglas en ingl茅s). Un PSBT es un papel que contiene toda la informaci贸n relevante de la transacci贸n. Esto incluye la cantidad de Bitcoin a gastar, el n煤mero total de miembros de una billetera (N), y los miembros requeridos de una billetera (M) que deben firmar para realizar una transacci贸n; las entradas y salidas, los testigos y los UTXOs. Los PSBTs a veces son necesarios para realizar transacciones multi-sig y tambi茅n pueden usarse para firmar transacciones individuales. Un PSBT es 煤til porque proporciona otra capa de seguridad y privacidad, ya que puede construirse y firmarse sin conexi贸n, minimizando el riesgo de exponer claves privadas.

La raz贸n por la que se llama 芦Parcialmente Firmado禄 es porque funciona como un contrato real. Cada parte debe firmar el contrato individualmente y luego pasarlo al siguiente firmante. As铆 que si te encuentras con un PSBT en la naturaleza, puede estar en 3 estados: no firmado, parcialmente firmado o completamente firmado. El estado completamente firmado se alcanza una vez que todos los firmantes requeridos han agregado su script de desbloqueo al PSBT. Por lo tanto, para 芦firmar禄 un PSBT, debes leer las entradas del PSBT y crear los scripts de desbloqueo para cada una. Veamos c贸mo se ve esto en el c贸digo Rust. Para este art铆culo, solo firmaremos entradas con testigos segregados.

Los paquetes que utilizaremos para este art铆culo son el paquete 芦bitcoin禄 para manipular nuestro PSBT y el paquete 芦anyhow禄 para manejar los errores.

Primero, crearemos la firma de nuestra funci贸n. Esta recibir谩 un PSBT, una clave privada y una ruta de derivaci贸n que usaremos para derivar la clave privada para esa ubicaci贸n espec铆fica (recuerda el concepto de 芦caja fuerte禄).

    
    fn sign_psbt(
        mut psbt: PartiallySignedTransaction,
        xprv: ExtendedPrivKey,
        derivation: &DerivationPath,
    ) -> Result<PartiallySignedTransaction> {
        ...
    }
    
  

A continuaci贸n, iteraremos sobre todas las entradas que necesitamos firmar. Tambi茅n necesitaremos un 铆ndice de la entrada para luego poder construir la firma. Todo lo que haremos a partir de ahora ser谩 dentro de este bucle for a.

    
    for (index, input) in psbt.inputs.iter_mut().enumerate() {
        ...
    }
    
  

Obtenemos el script de testigo, que contiene las firmas de los propietarios actuales de las salidas que se est谩n gastando en esta transacci贸n. Esto se utilizar谩 para verificar las entradas de la transacci贸n. Dado que estamos codificando solo para testigos segregados, lanzaremos un error si esta informaci贸n no est谩 disponible y dejaremos que la funci贸n principal de esta maneje la situaci贸n.

    
    let witness_script = input
        .witness_script
        .as_ref()
        .context("Missing witness script")?;
    
  
    
    let amount = input
        .witness_utxo
        .as_ref()
        .context("Witness utxo not found")?
        .value;
    
  

Hablamos brevemente sobre el sighash anteriormente; en estas l铆neas, crearemos el sighash, pero esperaremos para combinarlo con nuestra firma. Incluiremos toda la informaci贸n relevante y luego obtendremos el sighash para decirle a segwit_signature_hash聽qu茅 informaci贸n agregar al hash.

    
    let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx);
    let sighash = sighash_cache.segwit_signature_hash(
        index,
        witness_script,
        amount,
        get_sighash_type(input),
    )?;
    
  

En la 煤ltima parte del c贸digo, invocamos una funci贸n llamada get_sighash_type; esta funci贸n obtiene la informaci贸n del tipo de sighash de la entrada creada cuando se cre贸 el PSBT. Si esa informaci贸n no se incluy贸 en la entrada, por defecto utilizamos SIGHASH_ALL.

    
    fn get_sighash_type(input: &Input) -> EcdsaSighashType {
        input
            .sighash_type
            .and_then(|t| t.ecdsa_hash_ty().ok())
            .unwrap_or(EcdsaSighashType::All)
    }
    
  
    
    let mut input_keypairs = Vec::new();
    for (_, (fingerprint, sub_derivation)) in input.bip32_derivation.iter() {
        if fingerprint != &xprv.fingerprint(&secp) {
            continue;
        }
        let parent_xprv = derive_relative_xpriv(&xprv, &secp, derivation, sub_derivation)?;
        input_keypairs.push(parent_xprv.to_keypair(&secp));
    }
    if input_keypairs.is_empty() {
        return Err(anyhow!("No private keys to sign this psbt"));
    }
    
  

Vimos antes que para tener acceso a una caja fuerte, necesitamos conocer el edificio, la habitaci贸n y el n煤mero de la caja fuerte. Tambi茅n vimos que al tener la contrase帽a de una habitaci贸n, podemos derivar las contrase帽as de las cajas fuertes que hay dentro. Existe la posibilidad de que el propietario de una billetera de Bitcoin conozca la contrase帽a maestra (por ejemplo, del edificio) o solo la contrase帽a de una habitaci贸n. Por esa raz贸n, para saber la contrase帽a que necesitamos usar para abrir una caja fuerte, necesitamos combinar los caminos de derivaci贸n de la contrase帽a que poseemos con el camino absoluto que queremos alcanzar. Y eso es lo que hace la funci贸n get_partial_derivation. Puedes pensar en los caminos de derivaci贸n como un arreglo con las coordenadas de nuestra caja fuerte. Por ejemplo, [44, 1, 3] podr铆a representar el pa铆s en el que estamos (44), la ciudad (1) y el edificio (3). Si solo conozco la contrase帽a de la ciudad, entonces s茅 que las coordenadas son [44, 1]. Dado que la entrada nos da la combinaci贸n final para la caja fuerte, por ejemplo, [44, 1, 3, 2, 2], necesitamos derivar la contrase帽a desde la ciudad hasta la caja fuerte. Entonces nuestra funci贸n get_partial_derivation nos dar谩 exactamente este camino de derivaci贸n parcial: [3, 2, 2]. Nuestra segunda funci贸n, 鈥榙erive_relative_xpriv鈥, se encargar谩 de derivar la clave privada en aquella que necesitaremos para firmar.

    
    fn get_partial_derivation(
        derivation: &DerivationPath,
        sub_derivation: &DerivationPath,
    ) -> Result<DerivationPath> {
        if derivation.len() > sub_derivation.len() {
            return Err(anyhow!(
                "Can't get a partial derivation from a derivation greater than the sub derivation"
            ));
        }
        let partial = &sub_derivation[derivation.len()..];
        DerivationPath::try_from(partial).map_err(|e| anyhow!("{e}"))
    }
    
  
    
    fn derive_relative_xpriv(
        xprv: &ExtendedPrivKey,
        secp: &Secp256k1<All>,
        derivation: &DerivationPath,
        sub_derivation: &DerivationPath,
    ) -> Result<ExtendedPrivKey> {
        xprv.derive_priv(secp, &get_partial_derivation(derivation, sub_derivation)?)
            .map_err(|e| anyhow!("{e}"))
    }
    
  

La parte final de nuestro c贸digo es s贸lo la 芦firma禄. Obtenemos los pares de claves que recogimos en los pasos anteriores. Creamos un Mensaje, un contenedor para toda la informaci贸n necesaria para crear y firmar una transacci贸n v谩lida. Una vez que construimos nuestro mensaje, podemos finalmente firmarlo y a帽adir la firma parcial a la entrada como otra tupla, una tupla Clave P煤blica – Firma. Pero antes, tendremos que a帽adir a la entrada la informaci贸n sobre qu茅 tipo de sighash hemos utilizado para firmar los datos. Esto se utilizar谩 para verificar la firma m谩s adelante.

    
    for keypair in input_keypairs {
        let message = &Message::from_slice(&sighash)?;
        let signature = secp.sign_ecdsa(message, &keypair.secret_key());
        input.partial_sigs.insert(
            PublicKey::new(keypair.public_key()),
            set_sighash_type(signature, input),
        );
        secp.verify_ecdsa(message, &signature, &keypair.public_key())?;
    }
    
  

El tipo de sighash se establece utilizando la funci贸n get_sighash_type que usamos antes y combin谩ndola con la firma.

    
    fn set_sighash_type(signature: Signature, input: &Input) -> EcdsaSig {
        let sighash_type = get_sighash_type(input);
        EcdsaSig {
            sig: signature,
            hash_ty: sighash_type,
        }
    }
    
  

Por 煤ltimo, fuera de nuestro bucle for de entradas, devolvemos el PSBT a la persona que llama a la funci贸n sign_psbt para que el PSBT pueda ser firmado por el siguiente firmante.

    
            }
        Ok(psbt)
    }
    
  
    
    use anyhow::{anyhow, Context, Result};
    use bitcoin::psbt::Input;
    use bitcoin::secp256k1::ecdsa::Signature;
    use bitcoin::secp256k1::{All, Message, Secp256k1};
    use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey};
    use bitcoin::util::psbt::PartiallySignedTransaction;
    use bitcoin::util::sighash::SighashCache;
    use bitcoin::{EcdsaSig, EcdsaSighashType, PublicKey};
    
    fn set_sighash_type(signature: Signature, input: &Input) -> EcdsaSig {
        let sighash_type = get_sighash_type(input);
        EcdsaSig {
            sig: signature,
            hash_ty: sighash_type,
        }
    }
    
    fn get_sighash_type(input: &Input) -> EcdsaSighashType {
        input
            .sighash_type
            .and_then(|t| t.ecdsa_hash_ty().ok())
            .unwrap_or(EcdsaSighashType::All)
    }
    
    fn get_partial_derivation(
        derivation: &DerivationPath,
        sub_derivation: &DerivationPath,
    ) -> Result<DerivationPath> {
        if derivation.len() > sub_derivation.len() {
            return Err(anyhow!(
                "Can't get a partial derivation from a derivation greater than the sub derivation"
            ));
        }
        let partial = &sub_derivation[derivation.len()..];
        dbg!(&partial);
        DerivationPath::try_from(partial).map_err(|e| anyhow!("{e}"))
    }
    
    fn derive_relative_xpriv(
        xprv: &ExtendedPrivKey,
        secp: &Secp256k1<All>,
        derivation: &DerivationPath,
        sub_derivation: &DerivationPath,
    ) -> Result<ExtendedPrivKey> {
        xprv.derive_priv(secp, &get_partial_derivation(derivation, sub_derivation)?)
            .map_err(|e| anyhow!("{e}"))
    }
    
    fn sign_psbt(
        mut psbt: PartiallySignedTransaction,
        xprv: ExtendedPrivKey,
        derivation: &DerivationPath,
    ) -> Result<PartiallySignedTransaction> {
        let secp = Secp256k1::new();
        // <https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#user-content-Signer>
        for (index, input) in psbt.inputs.iter_mut().enumerate() {
            let witness_script = input
                .witness_script
                .as_ref()
                .context("Missing witness script")?;
            let amount = input
                .witness_utxo
                .as_ref()
                .context("Witness utxo not found")?
                .value;
            let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx);
            let sighash = sighash_cache.segwit_signature_hash(
                index,
                witness_script,
                amount,
                get_sighash_type(input),
            )?;
            let mut input_keypairs = Vec::new();
            for (_, (fingerprint, sub_derivation)) in input.bip32_derivation.iter() {
                if fingerprint != &xprv.fingerprint(&secp) {
                    continue;
                }
                let parent_xprv = derive_relative_xpriv(&xprv, &secp, derivation, sub_derivation)?;
                input_keypairs.push(parent_xprv.to_keypair(&secp));
            }
            if input_keypairs.is_empty() {
                return Err(anyhow!("No private keys to sign this psbt"));
            }
            for keypair in input_keypairs {
                let message = &Message::from_slice(&sighash)?;
                let signature = secp.sign_ecdsa(message, &keypair.secret_key());
                input.partial_sigs.insert(
                    PublicKey::new(keypair.public_key()),
                    set_sighash_type(signature, input),
                );
                secp.verify_ecdsa(message, &signature, &keypair.public_key())?;
            }
        }
        Ok(psbt)
    }
    
  

Posted

in

by

Tags:

Comentarios

Deja una respuesta

Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *