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.
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.
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:
- Generar un nuevo wallet + mnemonic (recomendado).
- Restaurar un wallet desde un mnemonic existente (cuando ya tienes uno).
- Guardar el wallet en un archivo
.ukey. - Cargar un wallet desde el disco.
- 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,AuthGroupsMnemonicKeyTypePublicKeyHexPrivateKeyHexopcional
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:
- Valida el mnemonic (BIP-39).
- Deriva una semilla del mnemonic + passphrase.
- Crea un keypair para el
KeyTypedado (p. ej.KeyTypeSecp256k1). - Calcula la
Addressa partir de la clave pública. - Devuelve un
UL_Walletque 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
WalletDatacon:Address,Enabled,Parent,AuthGroupsKeyType,PublicKeyHexMnemonicopcionalPrivateKeyHexopcional (solo siincludePrivateKeyestrue)
-
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:
addresses el hash SHA-256 de la clave pública en hex en minúsculas.keyTypese almacena como una cadena (p. ej."secp256k1") y se mapea internamente acrypto.KeyTypeSecp256k1.mnemonicestá presente aquí por conveniencia y recuperación; puedes omitirlo si no quieres mnemonics en los archivos.privateKeyHexse 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:
- Lee y analiza el JSON
WalletData. - Si
Mnemonicestá presente → llama aGenerateFromMnemonic(el mnemonic debe ser BIP-39 válido). - Si
PrivateKeyHexestá presente → reconstruye la clave desde hex. - 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,EnabledyAuthGroupspueden 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
Addressa 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.