Saltar al contenido principal

Billeteras

En este tutorial revisaremos cómo crear, guardar, recrear y usar una billetera. Cuando se realiza una transaction, el SDK firmará la transaction y la enviará a la red. Este sello criptográfico nos permite detectar cualquier modificación a los datos. Además, proporciona trazabilidad a la actividad de la billetera. Esto se debe a que todas las transactions estarán vinculadas a la billetera.

info

Una billetera se utiliza para crear y firmar transactions; además, la billetera tendrá una dirección única. Los nodes realizarán validaciones de la firma y la existencia de la billetera en el blockchain para esas transactions, por lo que es un requisito usar billeteras válidas al crear transactions.

info

Este tutorial asume que ya existe un blockchain configurado con ULedger.

En ULedger, una billetera representa una identidad on-chain:

  • Contiene un keypair (clave pública/privada).
  • Tiene una address única, derivada de la clave pública.
  • Se le pueden otorgar permisos a través de authGroups.
  • Se utiliza para firmar transactions que la red puede verificar.

Esta guía explica cómo:

  1. Generar una nueva billetera + mnemonic (recomendado).
  2. Restaurar una billetera desde un mnemonic existente (cuando ya existe uno).
  3. Guardar la billetera en un archivo .ukey.
  4. Cargar una billetera desde el disco.
  5. Recrear una billetera desde claves o JSON existentes.

Después de esto, la billetera estará lista para usarse en
👉 Crear Transactions con el Go SDK.


1. Prerrequisitos​

Antes de comenzar, se debe contar con una versión reciente de Go.

En el código Go, se usa principalmente:

import (
"fmt"
"github.com/ULedgerInc/go-sdk/pkg/crypto"
"github.com/ULedgerInc/go-sdk/pkg/wallet"
)

2. Estructura UL_Wallet (¿qué contiene una billetera?)​

El tipo principal es:

type UL_Wallet struct {
Address string `json:"address"`
Enabled bool `json:"enabled"`
Parent string `json:"parent"`
AuthGroups map[string]UL_AuthPermission `json:"authGroups"`
key crypto.ULKey `json:"-"`
}
  • Address – derivada de la clave pública (SHA-256 sobre el hex de la clave pública).
  • Enabled – ¿puede esta billetera usarse ahora mismo?
  • Parent – billetera padre opcional o identificador del propietario.
  • AuthGroups – permisos agrupados por nombre lógico (p. ej. "Leadership").
  • key – la clave criptográfica en memoria (no serializada directamente).

Para persistencia, el SDK usa una estructura JSON interna WalletData que puede incluir:

  • Address, Enabled, Parent, AuthGroups
  • Mnemonic
  • KeyType
  • PublicKeyHex
  • PrivateKeyHex opcional

Este JSON es lo que termina en los archivos .ukey.


3. Generar una nueva billetera + mnemonic (recomendado)​

La forma más segura y recomendada de crear una billetera es dejar que el SDK genere un mnemonic criptográficamente seguro BIP-39.

El Go SDK soporta estos valores de entropía:

  • wallet.Entropy128 (12 palabras)
  • wallet.Entropy160 (15 palabras)
  • wallet.Entropy192 (18 palabras)
  • wallet.Entropy224 (21 palabras)
  • wallet.Entropy256 (24 palabras, predeterminado)

Ejemplo:

package main

import (
"fmt"
"log"

"github.com/ULedgerInc/go-sdk/pkg/crypto"
"github.com/ULedgerInc/go-sdk/pkg/wallet"
)

func main() {
passphrase := "" // optional
keyType := crypto.KeyTypeSecp256k1
parent := ""
authGroups := map[string]wallet.UL_AuthPermission{}

w, mnemonic, err := wallet.GenerateNewWallet(
passphrase,
keyType,
parent,
authGroups,
wallet.DefaultEntropy, // defaults to Entropy256
)
if err != nil {
log.Fatalf("failed to generate new wallet: %v", err)
}

fmt.Println("Address:", w.Address)
fmt.Println("Mnemonic (store this safely!):", mnemonic)
}

⚠️ El mnemonic devuelto debe tratarse como un secreto de alta seguridad. Se recomienda guardarlo sin conexión si es posible.


4. Restaurar una billetera desde un mnemonic existente (cuando ya existe uno)​

Si ya se tiene un mnemonic BIP-39 válido (por ejemplo, generado anteriormente por el SDK), se puede recrear la misma billetera.

Ejemplo:

package main

import (
"fmt"
"log"

"github.com/ULedgerInc/go-sdk/pkg/crypto"
"github.com/ULedgerInc/go-sdk/pkg/wallet"
)

func main() {
mnemonic := "<paste your BIP-39 mnemonic here>"
passphrase := "" // optional (must match what was used originally)

// Optional but recommended: validate before attempting to restore
if !wallet.ValidateMnemonic(mnemonic) {
log.Fatal("invalid mnemonic: must be valid BIP-39")
}

w, err := wallet.GenerateFromMnemonic(mnemonic, passphrase, crypto.KeyTypeSecp256k1)
if err != nil {
log.Fatalf("failed to generate wallet from mnemonic: %v", err)
}

fmt.Println("Wallet address:", w.Address)
}

GenerateFromMnemonic:

  1. Valida el mnemonic (BIP-39).
  2. Deriva una semilla del mnemonic + passphrase.
  3. Crea un keypair para el KeyType dado (p. ej. KeyTypeSecp256k1).
  4. Calcula la Address a partir de la clave pública.
  5. Devuelve un UL_Wallet que puede firmar transactions.

💡 El mnemonic + passphrase es suficiente para recrear la misma billetera más adelante. Perderlo significa perder el acceso al keypair subyacente.


5. Guardar una billetera en un archivo .ukey​

Una vez que se tiene un UL_Wallet, se puede persistir con SaveToFile:

// w is your UL_Wallet
// mnemonic is the phrase associated with this wallet (optional but recommended)
includePrivateKey := false // set true only if the private key must be written to the file

if err := w.SaveToFile("wallets/my_wallet.ukey", mnemonic, includePrivateKey); err != nil {
log.Fatalf("failed to save wallet file: %v", err)
}

SaveToFile:

  • Verifica que el nombre del archivo termine con .ukey.

  • Escribe una estructura JSON WalletData con:

    • Address, Enabled, Parent, AuthGroups
    • KeyType, PublicKeyHex
    • Mnemonic opcional
    • PrivateKeyHex opcional (solo si includePrivateKey es true)
  • Usa el modo de archivo 0600 (solo lectura/escritura del propietario).

🔐 La mejor práctica es evitar guardar la clave privada cuando sea posible. El mnemonic debe ser el mecanismo de recuperación.

Ejemplo de archivo .ukey​

Después de guardar, un archivo de billetera puede verse así:

{
"address": "11e4876958847f93a82c39110cc86a2bcdf1adf3f725fa9ad8a70b6f5eae1bf1",
"enabled": true,
"parent": "",
"authGroups": {},
"mnemonic": "kiwi possible help find habit genre math lion alpha page loud arrow victory alone valve report magnet menu stand avocado online must trophy hire",
"keyType": "secp256k1",
"publicKeyHex": "0435C4D829A686D05ACE8256CAA73504F69BF5569D3E6122BEC54D64D7C31C8E6A1E873849440328BCD3216A18849909CBDE6E09DF81E7EC5DC7E17EE1865A63B1",
"privateKeyHex": "2310117A24CD8F086E429D1F52748FEC3EAD5C42E0910187143FE8C63CC0C0C1"
}

Algunos detalles a destacar:

  • address es el hash SHA-256 de la clave pública en hex en minúsculas.
  • keyType se almacena como una cadena (p. ej. "secp256k1") y se mapea internamente a crypto.KeyTypeSecp256k1.
  • mnemonic está presente aquí por conveniencia y recuperación; se puede omitir si no se desean mnemonics en los archivos.
  • privateKeyHex se incluye en este ejemplo, pero en producción puede ser preferible excluirlo y depender únicamente del mnemonic.

6. Cargar una billetera desde un archivo .ukey​

Para reconstruir una billetera desde un archivo .ukey, se usa LoadFromFile:

loaded, err := wallet.LoadFromFile("wallets/my_wallet.ukey", "")
if err != nil {
log.Fatalf("failed to load wallet: %v", err)
}

fmt.Println("Loaded wallet address:", loaded.Address)

Comportamiento de LoadFromFile:

  1. Lee y analiza el JSON WalletData.
  2. Si Mnemonic está presente → llama a GenerateFromMnemonic (el mnemonic debe ser BIP-39 válido).
  3. Si PrivateKeyHex está presente → reconstruye la clave desde hex.
  4. De lo contrario → recurre a solo clave pública (sin clave privada → no utilizable para firmar).

El argumento passphrase se reenvía a GenerateFromMnemonic cuando el archivo contiene un mnemonic.

Nota: Restaurar desde un mnemonic reconstruye la identidad criptográfica (keypair + address). Dependiendo del contenido del archivo y el comportamiento del SDK, los metadatos como Parent, Enabled y AuthGroups pueden necesitar ser reaplicados.


7. Cargar desde JSON sin procesar (string) con FromJson​

Si la billetera existe como una cadena JSON sin procesar (por ejemplo, proveniente de un CLI o una API), se puede cargar con FromJson:

raw := `{
"address": "11e4876958847f93a82c39110cc86a2bcdf1adf3f725fa9ad8a70b6f5eae1bf1",
"enabled": true,
"parent": "",
"authGroups": {},
"mnemonic": "kiwi possible help find habit genre math lion alpha page loud arrow victory ...",
"keyType": "secp256k1",
"publicKeyHex": "0435C4D829A686D0...",
"privateKeyHex": "2310117A24CD8F08..."
}`

w, err := wallet.FromJson(raw, "")
if err != nil {
log.Fatalf("failed to load wallet from JSON: %v", err)
}

fmt.Println("Address:", w.Address)

Esto usa la misma estructura WalletData internamente y reconstruye el crypto.ULKey interno basándose en KeyType, PublicKeyHex y PrivateKeyHex (si está presente).


8. Importar un keypair existente (GetWalletFromHex)​

Si ya se tienen claves públicas y privadas codificadas en hex, se pueden envolver en un UL_Wallet con GetWalletFromHex:

publicKeyHex := "04f2f0fd15ba3a7f4ba62cd705c4df80..." // uncompressed public key hex
privateKeyHex := "63f6062f2034bcbcc08bae2eaabee8dd..." // private key hex

w, err := wallet.GetWalletFromHex(publicKeyHex, privateKeyHex, crypto.KeyTypeSecp256k1)
if err != nil {
log.Fatalf("failed to import wallet from hex: %v", err)
}

fmt.Println("Imported wallet address:", w.Address)

GetWalletFromHex:

  • Valida los valores hex.
  • Reconstruye el crypto.ULKey.
  • Calcula la Address a partir de la clave pública.

9. Próximos pasos: Registrar billeteras​

Todo lo que hemos hecho hasta ahora existe localmente:

  • Generamos un keypair (y mnemonic).
  • Lo guardamos en un archivo .ukey.
  • Lo cargamos de vuelta en un UL_Wallet.

En este punto, la billetera aún no está registrada en ningún blockchain. Es una identidad criptográfica que puede usarse en uno o más blockchains de ULedger.

Para hacer que un blockchain conozca esta billetera (su clave pública, parent y authGroups), se envía una transaction especial de tipo TX_CREATE_WALLET:

  • El payload incluye la clave pública de la billetera, el tipo de clave, el parent y los auth groups.
  • La transaction se firma y se envía como cualquier otra transaction.
  • Una vez incluida en un bloque, los nodes pueden validar esta billetera cuando firme transactions futuras.

👉 Continuar en: Registrar una Billetera en un Blockchain (próximamente) para ver cómo construir y enviar una transaction TX_CREATE_WALLET.