This a Algorand HDWallet implementation trial, complying with bip32 & slip-0010 for ED25519 curve.
Also, basic deterministic key derivation from 25 words mnemomic is implemented too using native go sdks. (Thanks to Yieldly repo here)
- Only hardened public keys are supported, because normal/public derivations (extended public key to public key) is not possible for ED25519 curve.
- Algorand coin code is 283 according to slip-0044. So derivation paths may be
m/44'/283'/0'/1'/100'
or simplym/1'/0'/0'
(Not mandating any format...) - ed25519 test vectors 1 & 2 defined with SLIP-0010 are implemented.
package main
import (
"fmt"
"github.com/polarbit/hdwallet"
)
func main() {
mnemonic24 := "segment inhale symptom olive cheese tissue vacuum lazy sketch salt enroll wink oyster hen glory food weasel comic glow legal cute diet fun real"
path := "m/44'/283'/0'"
a, _, _ := hdwallet.DeriveAddressFromMnemonic(mnemonic24, path)
fmt.Printf("Address: %s\n", a)
}
slip0010 creates extended public keys padded a zero byte (0x00) from left.
This 33 bytes public key is not compatible with Alogrand address generation (32 bytes required).
So while creating Algorand an address, we ignore the padded zero byte.
ed25519 key generation produces a 64 byte private key.
Second half of this private key is public key of the curve, pub = priv[32:]
.
slip0010 uses the first half (32 byest) of that for key generation
Algorand-Sdk also uses the first half of that private key for mnemonics.
This is the Algorand kmd code that generates deterministec wallets. I expect same derivations from same seed; but did not tested myself.
Below are two main/low-level methods that helps to understand low levels of Algorand tx signing.
// rawSignTransaction signs the msgpack-encoded tx (with prepended "TX" prefix), and returns the sig and txid
func rawSignTransaction(sk ed25519.PrivateKey, tx types.Transaction) (s types.Signature, txid string, err error) {
toBeSigned := rawTransactionBytesToSign(tx)
// Sign the encoded transaction
signature := ed25519.Sign(sk, toBeSigned)
// Copy the resulting signature into a Signature,
// and check that it's the expected length
n := copy(s[:], signature)
if n != len(s) {
err = errInvalidSignatureReturned
return
}
// Populate txID
txid = txIDFromRawTxnBytesToSign(toBeSigned)
return
}
// rawTransactionBytesToSign returns the byte form of the tx that
// we actually sign and compute txID from.
func RawTransactionBytesToSign(tx types.Transaction) []byte {
var txidPrefix = []byte("TX")
// Encode the transaction as msgpack
encodedTx := msgpack.Encode(tx)
// Prepend the hashable prefix
msgParts := [][]byte{txidPrefix, encodedTx}
return bytes.Join(msgParts, nil)
}
go-algorand/daemon/kmd/wallet/driver/sqlite_crypto.go
Run single test: go test ./... -run "MnemonicToSeed" -v
Run integration test: TEST_INT=true go test ./... -run "AlgorandPayment" -v
Yieldly - Algorand Deterministic Account Derivation
Algorand Developer Docs - Offline Signing
- Refactor: logs, types ([]byte), errors (better messages).
- Add TX building, signing and broadcasting capability. (We already do this in payment integration test)
- Import & export xpriv, and generate keys from it.
- Use environment variables in integration tests for mnemomics.
- ... support other curves when Algorand is done. (maybe no or just ed25519 coins!)