Saltar al contenido principal

Wallets

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

info

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

info

Este tutorial asume que ya tienes un blockchain existente configurado con ULedger.

En ULedger, un wallet 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.

En esta guía aprenderás cómo:

  1. Generar un nuevo wallet + mnemonic (recomendado).
  2. Restaurar un wallet desde un mnemonic existente (cuando ya tienes uno).
  3. Guardar el wallet en un archivo .ukey.
  4. Cargar un wallet desde el disco.
  5. Recrear un wallet desde claves o JSON existentes.

Después de esto, estarás listo para usar tu wallet en
👉 Crear Transactions con el Go SDK.


1. Prerrequisitos

Antes de comenzar, asegúrate de tener una versión reciente de Go.

En tu código Go, principalmente usarás:

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

2. Estructura UL_Wallet (¿qué contiene un wallet?)

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 este wallet usarse ahora mismo?
  • Parent – wallet 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 tus archivos .ukey.


3. Generar un nuevo wallet + mnemonic (recomendado)

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

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)
}

⚠️ Trata el mnemonic devuelto como un secreto de alta seguridad. Guárdalo sin conexión si es posible.


4. Restaurar un wallet desde un mnemonic existente (cuando ya tienes uno)

Si ya tienes un mnemonic BIP-39 válido (por ejemplo, generado anteriormente por el SDK), puedes recrear el mismo wallet.

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 el mismo wallet más adelante. Perderlo significa perder el acceso al keypair subyacente.


5. Guardar un wallet en un archivo .ukey

Una vez que tienes un UL_Wallet, puedes persistirlo con SaveToFile:

// w is your UL_Wallet
// mnemonic is the phrase associated with this wallet (optional but recommended)
includePrivateKey := false // set true only if you really need the private key in 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. Deja que el mnemonic sea tu mecanismo de recuperación.

Ejemplo de archivo .ukey

Después de guardar, un archivo wallet 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; puedes omitirlo si no quieres mnemonics en los archivos.
  • privateKeyHex se incluye en este ejemplo, pero en producción podrías preferir excluirlo y depender únicamente del mnemonic.

6. Cargar un wallet desde un archivo .ukey

Para reconstruir un wallet desde un archivo .ukey, 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 de tu 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 tu wallet vive como una cadena JSON sin procesar (por ejemplo, proveniente de un CLI o una API), puedes cargarlo 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 tienes claves públicas y privadas codificadas en hex, puedes envolverlas 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 wallets

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, el wallet aún no está registrado en ningún blockchain. Es simplemente una identidad criptográfica que podría usarse en uno o más blockchains de ULedger.

Para hacer que un blockchain conozca este wallet (su clave pública, parent y authGroups), envías una transaction especial de tipo TX_CREATE_WALLET:

  • El payload incluye la clave pública del wallet, 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 este wallet cuando firme transactions futuras.

👉 Continúa en: Registrar un Wallet en un Blockchain (próximamente) para ver cómo construir y enviar una transaction TX_CREATE_WALLET.