Electrum 2FA Wallet - Derivación del tercer XPub

Estoy trabajando en un proyecto que requiere que tome un mnemotécnico de Electrum, determine su tipo y obtenga las direcciones correspondientes para esa billetera.

Encontré mucha información sobre cómo determinar el tipo mnemotécnico y convertirlo en la semilla maestra, pero me cuesta entender cómo se derivan las direcciones para la billetera 2FA. Para ser más exactos, pude derivar ambos pares de claves extendidos ( x1/y x2/en el archivo de la billetera), pero parece que no puedo derivar el tercer xpub ( x3/en el archivo de la billetera).

Revisé el código fuente y parece que esta clave se deriva de la combinación de ambas claves xpub (las claves deben estar ORDENADAS ) que se pueden derivar de la semilla maestra ( x1/y x2/), usándolas SHA-256y usándolas como índice al derivar el tercer xpub. Se utiliza un xpub codificado como raíz.

Tengo problemas para entender/implementar esto porque el SHA-256algoritmo devuelve 32 bytes de datos y el índice secundario debe tener 4 bytes (según la BIP32especificación).

Aquí está el código de Electrum que maneja esto.

def get_user_id(storage):
    def make_long_id(xpub_hot, xpub_cold):
        return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold])))
    xpub1 = storage.get('x1/')['xpub']
    xpub2 = storage.get('x2/')['xpub']
    long_id = make_long_id(xpub1, xpub2)
    short_id = hashlib.sha256(long_id).hexdigest()
    return long_id, short_id

def make_xpub(xpub, s):
    version, _, _, _, c, cK = deserialize_xpub(xpub)
    cK2, c2 = bitcoin._CKD_pub(cK, c, s)
    return bitcoin.serialize_xpub(version, c2, cK2)

Las funciones se llaman así:

xpub1 = wizard.storage.get('x1/')['xpub']
xpub2 = wizard.storage.get('x2/')['xpub']
# Generate third key deterministically.
long_user_id, short_id = get_user_id(wizard.storage)
xpub3 = make_xpub(signing_xpub, long_user_id)

Tiene long_user_id32 bytes de largo ya que es el resultado de la SHA-256función y luego se usa para llamar a la make_xpub()función que se supone que debe tomar 4 bytes de datos como índice secundario.

¿Que me estoy perdiendo aqui? Cualquier entrada es apreciada.

Respuestas (1)

Después de investigar un poco más el código fuente de Electrum Wallet, pude encontrar una implementación funcional en JavaScript.

El problema que describí en mi pregunta fue que long_user_idtiene 32 bytes, mientras que solo necesita 4 bytes para derivar una clave secundaria. Resulta que el tercer xpub en realidad no es un hijo del xpub de firma codificado, sino una clave raíz derivada de esa clave de firma (es decir, tiene un código de cadena completamente diferente y su índice es 0).

Aquí está la función que escribí que toma los dos xpubs de la billetera (que se encuentran debajo x1/y x2/en el archivo de la billetera/almacén de claves) y el xpub codificado de Trustedcoin y deriva el tercer xpub.

import HDKey from 'hdkey';
import crypto from 'crypto';
import secp256k1 from 'secp256k1';

function getCosignerXPub(xpub_x1, xpub_x2){
    const signing_xpub = "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL";

    const xpubs = [xpub_x1, xpub_x2].sort().join("");
    const long_user_id = new Buffer(sha256(xpubs), 'hex');

    const hdkey = HDKey.fromExtendedKey(signing_xpub);

    const data = Buffer.concat([hdkey.publicKey, long_user_id]);

    const I = crypto.createHmac('sha512', hdkey.chainCode).update(data).digest();

    const IL = I.slice(0, 32);
    const IR = I.slice(32);

    const hd = new HDKey(hdkey.versions);

    hd.publicKey = secp256k1.publicKeyTweakAdd(hdkey.publicKey, IL, true);
    hd.chainCode = IR;

    return hd.publicExtendedKey;
}