centrifuge / go-substrate-rpc-client

Substrate RPC client for go aka GSRPC
Apache License 2.0
203 stars 178 forks source link

sign extrinsic with ed25519 curve. #307

Closed bhaagiKenpachi closed 10 months ago

bhaagiKenpachi commented 1 year ago

@cdamian I want to sign the extrinsic payload with ed25519 curve. Extrinsic consists of balances::transfer and system::remark pallets. Example: in remark, the message Is "testing", transfer -> x amount.

I have tried the below code but getting error:

package main

const (
    endpoint = "ws://localhost:9944"
    westend  = "wss://westend-rpc.polkadot.io"
)

func main() {

    api, err := gsrpc.NewSubstrateAPI(endpoint)
    if err != nil {
        fmt.Errorf("error %w", err)
    }

    meta, err := api.RPC.State.GetMetadataLatest()

    if err != nil {
        fmt.Errorf("error %w", err)
    }

    dest, err := gsrpcTypes.NewMultiAddressFromHexAccountID("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48")

    if err != nil {
        panic(err)
    }

    memo := gsrpcTypes.NewData([]byte("memo:ADD:DOT.DOT:dojima1nh4y3gqxsn7ymm9t45zwsz3h8p9tm7pev8my62"))
    //memoBytes, err := codec.Encode(memo)
    if err != nil {
        panic(err)
    }

    call1, err := gsrpcTypes.NewCall(meta, "System.remark", []byte("memo:ADD:DOT.DOT:dojima1nh4y3gqxsn7ymm9t45zwsz3h8p9tm7pev8my62"))
    if err != nil {
        panic(err)
    }

    //keyringPair := signature.KeyringPair{Address: kp.Public().Hex(), PublicKey: kp.Public().Encode(), URI: mnemonic}

    if err != nil {
        panic(err)
    }

    call2, err := gsrpcTypes.NewCall(meta, "Balances.transfer", dest, gsrpcTypes.NewUCompactFromUInt(1000000))
    if err != nil {
        panic(err)
    }

    batchCall, err := gsrpcTypes.NewCall(meta, "Utility.batch_all", []gsrpcTypes.Call{call1, call2})
    if err != nil {
        panic(err)
    }

    genesisHash, err := api.RPC.Chain.GetBlockHash(0)
    if err != nil {
        fmt.Errorf("error %w", err)
    }

    kp, err := Ed25519_KPFromSeedPhrase(mnemonic, 42)
    var sub *author.ExtrinsicStatusSubscription
    //fmt.Println(kp.Address)
    //for {
    aliceStorageKey, err := gsrpcTypes.CreateStorageKey(meta, "System", "Account", kp.PublicKey)

    if err != nil {
        panic(err)
    }

    var accountInfo gsrpcTypes.AccountInfo
    ok, err := api.RPC.State.GetStorageLatest(aliceStorageKey, &accountInfo)
    fmt.Println(accountInfo)
    if err != nil || !ok {
        panic(err)
    }

    rv, err := api.RPC.State.GetRuntimeVersionLatest()
    if err != nil {
        panic(err)
    }

    ext := gsrpcTypes.NewExtrinsic(batchCall)
    nonce := uint32(accountInfo.Nonce)
    //fmt.Println(nonce)
    signOpts := gsrpcTypes.SignatureOptions{
        BlockHash:          genesisHash, // using genesis since we're using immortal era
        Era:                gsrpcTypes.ExtrinsicEra{IsMortalEra: false},
        GenesisHash:        genesisHash,
        Nonce:              gsrpcTypes.NewUCompactFromUInt(uint64(nonce)),
        SpecVersion:        rv.SpecVersion,
        Tip:                gsrpcTypes.NewUCompactFromUInt(0),
        TransactionVersion: rv.TransactionVersion,
    }
    ed25519Extrinsic := &Edd25519_Extrinsic{Extrinsic: ext}

    if err := ed25519Extrinsic.Ed25519Sign(kp, signOpts); err != nil {
        panic(err)
    }

    if err != nil {
        fmt.Println(err)
    }

    sub, err = api.RPC.Author.SubmitAndWatchExtrinsic(ext)

    if err != nil {
        fmt.Printf("extrinsic submit failed %v \n\n", err)
        //continue
    }

    //  break
    //}

    defer sub.Unsubscribe()

    select {
    case <-time.After(1 * time.Minute):
        panic("Timeout reached")
    case st := <-sub.Chan():
        extStatus, _ := st.MarshalJSON()
        fmt.Println("Done with status -", string(extStatus))
    case err := <-sub.Err():
        panic(err)
    }
}

type Edd25519_Extrinsic struct {
    Extrinsic types.Extrinsic
}

// KeyringPairFromSecret creates KeyPair based on seed/phrase and network
// Leave network empty for default behavior
func Ed25519_KPFromSeedPhrase(seedOrPhrase string, network uint8) (signature.KeyringPair, error) {
    scheme := ed25519.Scheme{}
    kyr, err := subkey.DeriveKeyPair(scheme, seedOrPhrase)
    if err != nil {
        return signature.KeyringPair{}, err
    }

    ss58Address, err := kyr.SS58Address(network)
    if err != nil {
        return signature.KeyringPair{}, err
    }

    var pk = kyr.Public()

    return signature.KeyringPair{
        URI:       seedOrPhrase,
        Address:   ss58Address,
        PublicKey: pk,
    }, nil
}

// Sign signs data with the private key under the given derivation path, returning the signature. Requires the subkey
// command to be in path
func Sign(data []byte, privateKeyURI string) ([]byte, error) {
    // if data is longer than 256 bytes, hash it first
    if len(data) > 256 {
        h := blake2b.Sum256(data)
        data = h[:]
    }

    scheme := ed25519.Scheme{}
    kyr, err := subkey.DeriveKeyPair(scheme, privateKeyURI)
    if err != nil {
        return nil, err
    }

    sig, err := kyr.Sign(data)
    if err != nil {
        return nil, err
    }

    return sig, nil
}

// Verify verifies data using the provided signature and the key under the derivation path. Requires the subkey
// command to be in path
func Verify(data []byte, sig []byte, privateKeyURI string) (bool, error) {
    // if data is longer than 256 bytes, hash it first
    if len(data) > 256 {
        h := blake2b.Sum256(data)
        data = h[:]
    }

    scheme := ed25519.Scheme{}
    kyr, err := subkey.DeriveKeyPair(scheme, privateKeyURI)
    if err != nil {
        return false, err
    }

    if len(sig) != 64 {
        return false, errors.New("wrong signature length")
    }

    v := kyr.Verify(data, sig)

    return v, nil
}

func (e *Edd25519_Extrinsic) Ed25519Sign(signer signature.KeyringPair, signatureOpts types.SignatureOptions) error {
    if e.Extrinsic.Type() != types.ExtrinsicVersion4 {
        return fmt.Errorf("unsupported extrinsic version: %v (isSigned: %v, type: %v)", e.Extrinsic.Version, e.Extrinsic.IsSigned(), e.Extrinsic.Type())
    }
    mb, err := codec.Encode(e.Extrinsic.Method)
    if err != nil {
        return err
    }

    era := signatureOpts.Era
    if !signatureOpts.Era.IsMortalEra {
        era = types.ExtrinsicEra{IsImmortalEra: true}
    }

    payload := types.ExtrinsicPayloadV4{
        ExtrinsicPayloadV3: types.ExtrinsicPayloadV3{
            Method:      mb,
            Era:         era,
            Nonce:       signatureOpts.Nonce,
            Tip:         signatureOpts.Tip,
            SpecVersion: signatureOpts.SpecVersion,
            GenesisHash: signatureOpts.GenesisHash,
            BlockHash:   signatureOpts.BlockHash,
        },
        TransactionVersion: signatureOpts.TransactionVersion,
    }
    signerPubKey, err := types.NewMultiAddressFromAccountID(signer.PublicKey)
    if err != nil {
        return err
    }

    b, err := codec.Encode(payload)

    if err != nil {
        return err
    }

    sig, err := Sign(b, signer.URI)

    if err != nil {
        return err
    }
    extSig := types.ExtrinsicSignatureV4{
        Signer:    signerPubKey,
        Signature: types.MultiSignature{IsEd25519: true, AsEd25519: types.NewSignature(sig)},
        Era:       era,
        Nonce:     signatureOpts.Nonce,
        Tip:       signatureOpts.Tip,
    }

    e.Extrinsic.Signature = extSig

    // mark the extrinsic as signed
    e.Extrinsic.Version |= types.ExtrinsicBitSigned

    ok, err := Verify(b, e.Extrinsic.Signature.Signature.AsEd25519[:], signer.URI)
    if !ok {
        fmt.Println(err)
    }
    return nil
}

extrinsic submit failed Unknown Transaction Validity

panic: runtime error: invalid memory address or nil pointer dereference panic: runtime error: invalid memory address or nil pointer dereference

bhaagiKenpachi commented 1 year ago

@cdamian Can you check above error?

cdamian commented 1 year ago

@bhaagiKenpachi We'll have to look into this and get back to you. Unfortunately, I cannot provide an answer at this point.

bhaagiKenpachi commented 1 year ago

@cdamian Please let me know status on this. I know you have lot of things on plate but ed25519 signing support would be good.

bhaagiKenpachi commented 1 year ago

@cdamian Any update on this.

cdamian commented 10 months ago

@bhaagiKenpachi Unfortunately, this is not something that we plan on adding soon.