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.
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.
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:
- Generar una nueva billetera + mnemonic (recomendado).
- Restaurar una billetera desde un mnemonic existente (cuando ya existe uno).
- Guardar la billetera en un archivo
.ukey. - Cargar una billetera desde el disco.
- 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,AuthGroupsMnemonicKeyTypePublicKeyHexPrivateKeyHexopcional
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:
- 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 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
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. 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:
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; se puede omitir si no se desean mnemonics en los archivos.privateKeyHexse 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:
- 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 del archivo y el comportamiento del SDK, los metadatos como
Parent,EnabledyAuthGroupspueden 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
Addressa 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.