흐름에 구축 | FCL 배우기 - 14. 개인 키로 트랜잭션에 서명하여 체인 상태를 변경하는 방법

머리말



지난번에는 브라우저에서 편안하게 Lilico 및 Blocto 지갑으로 거래에 서명하는 방법을 다루었습니다. 그러나 브라우저를 사용할 수 없는 백엔드(예: 서버 측)에서 동일한 작업을 수행해야 하는 경우가 매우 일반적입니다.

친구여, 이것이 바로 우리가 당신에게 가르칠 것이므로 두려워하지 마십시오. 이 문서의 자료를 통해 작업한 후 다음 방법을 알게 됩니다.
  • 트랜잭션에 대해 다른 역할을 수행합니다
  • .
  • multisig 프로세스
  • 에 대한 기본적인 이해
  • 개인 키
  • 로 트랜잭션에 서명

    1단계 - 설치


    "@onflow/fcl" , ellipticsha3 패키지를 프로젝트 종속성으로 추가합니다.

    2단계 - 서명자 만들기



    새 파일을 만들고 이름을 지정해 보겠습니다signer.js . 파일은 아무 이름이나 가질 수 있으며 여기서는 의미론적 의미가 있는 것을 선택합니다. 서명자는 3가지 기능이 필요합니다.
  • 첫 번째는 hashMessageHex라고 합니다. SHA3 algorithm을 사용하여 트랜잭션 메시지를 해시하는 데 사용됩니다. 우리가 SHA3를 사용하는 이유는 이전 기사 중 하나에서 설명한 Testnet Faucet에서 계정 생성 중에 Hash Algorithm로 선택했기 때문입니다.
    기능 자체는 매우 간단합니다. 특별히 압축되고 16진수 문자열로 표시된 트랜잭션 메시지를 update에 의해 노출된 SHA3 메서드에 공급한 다음 digest 메서드에서 결과를 반환합니다.

  • const hashMessageHex = (msgHex) => {
      const sha = new SHA3(256);
      sha.update(Buffer.from(msgHex, "hex"));
      return sha.digest();
    };
    


  • 다음 함수는 signWithKey를 호출합니다. 이를 사용하여 개인 키로 해시된 거래 메시지에 서명합니다. 이제 이것은 정말 복잡한 주제이며 이것을 단순히 복사하여 붙여넣고 나중에 암호 연구를 수행하면 많은 코너를 잘라낼 것이라고 가정해 봅시다, mkey? 😅
    tldr: 서명을 생성하기 위해 Elliptic Curve Digital Signature Algorithm을 사용합니다.

  • const signWithKey = (privateKey, msgHex) => {
      const key = curve.keyFromPrivate(Buffer.from(privateKey, "hex"));
      const sig = key.sign(hashMessageHex(msgHex));
    
      const n = 32;
      const r = sig.r.toArrayLike(Buffer, "be", n);
      const s = sig.s.toArrayLike(Buffer, "be", n);
    
    return Buffer.concat([r, s]).toString("hex");
    };
    


  • 마지막은 signer 함수로, 서명할 사용자의 정보를 생성하는 Authorization Function 함수와 이 정보를 사용하여 서명을 생성하는 signing function 함수로 사용됩니다.

  • export const signer = async (account) => {
      // We are hard coding these values here, but you can pass those values from outside as well.
      // For example, you can create curried function: 
      // const signer = (keyId, accountAdddress, pkey) => (account) => {...}
      // and then create multiple signers for different key indices 
    
      const keyId = Number(0); // always ensure that your keyId is a number not a string
      const accountAddress = "0x5593df7d286bcdb8";
      const pkey =
        "248f1ea7b4a058c39dcc97d91e6a5d0aa7afbc931428561b6efbc7bd0f5e0875";
    
      // authorization function need to return an account
      return {
        ...account, // bunch of defaults in here, we want to overload some of them though
        tempId: `${accountAddress}-${keyId}`, // tempIds are more of an advanced topic, for 99% of the times where you know the address and keyId you will want it to be a unique string per that address and keyId
        addr: sansPrefix(accountAddress), // the address of the signatory, currently it needs to be without a prefix right now
        keyId // this is the keyId for the accounts registered key that will be used to sign, make extra sure this is a number and not a string
    
        // This is where magic happens! ✨
        signingFunction: async (signable) => {
          // Singing functions are passed a signable and need to return a composite signature
          // signable.message is a hex string of what needs to be signed.
          const signature = await signWithKey(pkey, signable.message);
    
          return {
            addr: withPrefix(accountAddress), // needs to be the same as the account.addr but this time with a prefix, eventually they will both be with a prefix
            keyId, // needs to be the same as account.keyId, once again make sure its a number and not a string
            signature // this needs to be a hex string of the signature, where signable.message is the hex value that needs to be signed
          };
        }
      };
    };
    


    ✨Please, note, that signingFunction is asynchronous, meaning that it can use Promises inside of its body to get a signature out of extension or remote server. Which is super handy, when you want to handle gas fees for your users 😉



    3단계 - FCL 설정




    import { config, query, mutate, tx } from "@onflow/fcl";
    import { signer } from "./signer"
    
    // Contrary to our wallet signing example, we don't need most of it in our config now
    // so we'll get back to simple version
    config({
      "accessNode.api": "https://rest-testnet.onflow.org",
      "0xBasic": "0xafabe20e55e9ceb6"
    });
    


    4단계 - readCounter 구현



    완전히 동일하게 작동하기 때문에 이전 기사에서 동일한 기능을 복사합니다.

    const readCounter = async () => {
      const cadence = `
        import Basic from 0xBasic
    
        pub fun main():UInt{
          return Basic.counter
        }
      `;
      const counter = await query({ cadence });
      console.log({ counter });
    };
    


    5단계 - shiftCounter 구현


    shiftCounter 함수도 거의 동일합니다. 유일한 차이점은 이번에는 signer 함수를 사용하여 모든 역할을 채울 것이라는 것입니다. 또한 console.timeconsole.timeEnd 메서드를 사용하여 트랜잭션을 봉인하는 데 걸린 시간을 기록합니다.

    const shiftCounter = async (value) => {
      console.log("%cSigning Transaction", `color: teal`);
    
      // Our Cadence code. Notice the use of alias here
      const cadence = `
        import Basic from 0xBasic
    
        transaction(shift: UInt8){
          prepare(signer: AuthAccount){
            Basic.incrementCounterBy(shift)
          }
        }
      `;
    
      // List of arguments
      const args = (arg, t) => [arg(value.toString(), t.UInt8)];
      const proposer = signer;
      const payer = signer;
      const authorizations = [signer];
    
      // "mutate" method will return us transaction id
      const txId = await mutate({
        cadence,
        args,
        proposer,
        payer,
        authorizations,
        limit: 999
      });
    
      console.log(`Submitted transaction ${txId} to the network`);
      console.log("%cWaiting for transaction to be sealed...", `color: teal`);
    
      const label = "Transaction Sealing Time";
      console.time(label);
    
        // We will use transaction id in order to "subscribe" to it's state change and get the details
      // of the transaction
      const txDetails = await tx(txId).onceSealed();
    
      console.timeEnd(label);
      return txDetails;
    };
    


    드디어



    파일 끝에 IIFE를 추가하고 방금 정의한 메서드로 채우겠습니다.

    (async () => {
      console.clear();
      await readCounter();
    
      const txDetails = await shiftCounter(12);
      console.log({ txDetails });
    
      // we will call "readCounter" function second time to ensure 
      // that value of counter has changed
      await readCounter();
    })();
    


    먼지가 가라앉은 후 콘솔의 출력이 비슷해야 합니다.

    {counter: "655"}
    Signing Transaction
    Submitted transaction d88e98687dd98f7597aca9afaf3daaba788f644f90003c9b144bfa13440fd9ab to the network 
    Waiting for transaction to be sealed...
    Transaction Sealing Time: 14749.60000000149ms 
    {txDetails: Object}
    {counter: "667"}
    




    결국 그렇게 어렵지는 않았죠? 😉

    다음 시간까지! 👋

    자원



  • 전체 소스 코드 - https://codesandbox.io/s/dev-to-14-mutate-with-pkey-d0jsj0

  • 패키지 - SHA3 - https://www.npmjs.com/package/sha3

  • 패키지 - 타원형 - https://www.npmjs.com/package/elliptic

  • FCL - 인증 기능 - https://docs.onflow.org/fcl/reference/api/#authorization-function

  • 유용할 수 있는 기타 리소스:

  • Flow Docs 사이트 - https://docs.onflow.org/ - Flow 블록체인 및 상호 작용 방법에 대한 자세한 정보

  • Flow Portal - https://flow.com/ - Flow 진입점

  • FCL JS - https://github.com/onflow/fcl-js - 소스 코드 및 FCL JS 라이브러리에 기여하는 기능

  • 케이던스 - https://docs.onflow.org/cadence/ - 케이던스 소개

  • Codesandbox - https://codesandbox.io - 빠른 프로토타이핑을 지원하는 놀라운 브라우저 내 IDE
  • 좋은 웹페이지 즐겨찾기