Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address.ParseAddress function failed #99

Open
taurenshaman opened this issue Nov 11, 2022 · 1 comment
Open

Address.ParseAddress function failed #99

taurenshaman opened this issue Nov 11, 2022 · 1 comment

Comments

@taurenshaman
Copy link

Here is my testing data:

address = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq07rexgja7sgh0lw3k9rgc430dsvlgqnkctwvhwl";
message = "hello, world";
signature = "0x08bacbcd0fc5420716f41b5d9b546825b794a6f1266d984fe64e551e14f87f2b31e47cf36af667d9630ff8810917efe9a1681177cf5799818fc67fa5f88fc4fc01";

calling: 
var script = CKBAddress.ParseAddress( address, "ckb" );

image

When I test some code, the ParseAddress always failed.
image

It always throw exception of invalid checksum
image

@taurenshaman
Copy link
Author

taurenshaman commented Nov 11, 2022

  • I updated Bech32 class to support bech32m.
  • I changed the default method to ParseShortAddress, and added a new ParseAddress to support newest CKB address.

Usage:

Bech32.Initialize("bech32m"); // or "bech32"
Bech32.Decode(...) / Bech32.Encode(...)

Bech32 class

  // Ref: https://github.com/nervosnetwork/tippy/blob/develop/src/Ckb.Address/Bech32.cs
  public class Bech32 {
    static readonly string CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";

    static readonly int[] GENERATOR = new int[] { 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 };

    public class EncodingNames {
      public const string Bech32 = "bech32";
      public const string Bech32m = "bech32m";
    }
    public enum EncodingFlips {
      Bech32 = 1,
      Bech32m = 0x2bc830a3
    }

    static int CheckValue = 1;
    static string Mode = EncodingNames.Bech32;
    public static void Initialize(string mode) {
      if (mode == EncodingNames.Bech32m) {
        Mode = EncodingNames.Bech32;
        CheckValue = (int)EncodingFlips.Bech32m;
      }
      else {
        Mode = EncodingNames.Bech32m;
        CheckValue = (int)EncodingFlips.Bech32;
      }
    }

    static int Polymod(int[] values) {
      int chk = 1;

      foreach (int v in values) {
        int top = chk >> 25;
        chk = ( chk & 0x1ffffff ) << 5 ^ v;
        for (int i = 0; i < 5; i++) {
          if (( ( top >> i ) & 1 ) == 1) {
            chk ^= GENERATOR[i];
          }
        }
      }

      return chk;
    }

    static int[] HrpExpand(string hrp) {
      List<int> ret = new List<int>();
      foreach (char c in hrp) {
        ret.Add( c >> 5 );
      }
      ret.Add( 0 );
      foreach (char c in hrp) {
        ret.Add( c & 31 );
      }
      return ret.ToArray();
    }

    static bool VerifyChecksum(string hrp, int[] data) {
      return Polymod( HrpExpand( hrp ).Concat( data ).ToArray() ) == CheckValue;
    }

    static int[] CreateChecksum(string hrp, int[] data) {
      int[] values = HrpExpand( hrp ).Concat( data ).Concat(
          new int[] { 0, 0, 0, 0, 0, 0 } ).ToArray();
      int polymod = Polymod( values ) ^ CheckValue;
      int[] ret = new int[6];
      for (int i = 0; i < ret.Length; i++) {
        ret[i] = ( polymod >> 5 * ( 5 - i ) ) & 31;
      }
      return ret;
    }

    internal static string Encode(string hrp, int[] data) {
      if (hrp.Length < 1) {
        throw new Exception( "invalid hrp" );
      }

      foreach (char c in hrp) {
        if (c < 33 || c > 126) {
          throw new Exception( "too long" );
        }
      }

      if (hrp.ToUpper() != hrp && hrp.ToLower() != hrp) {
        throw new Exception( "mix case" );
      }

      bool lower = hrp.ToLower() == hrp;
      hrp = hrp.ToLower();
      int[] combined = data.Concat( CreateChecksum( hrp, data ) ).ToArray();

      StringBuilder ret = new StringBuilder();
      ret.Append( hrp );
      ret.Append( '1' );

      foreach (int p in combined) {
        if (p < 0 || p > CHARSET.Length) {
          throw new Exception( "invalid data" );
        }
        ret.Append( CHARSET[p] );
      }

      if (lower) {
        return ret.ToString();
      }
      return ret.ToString().ToUpper();
    }

    internal static (string Hrp, int[] Data) Decode(string bechString) {
      if (bechString.ToLower() != bechString && bechString.ToUpper() != bechString) {
        throw new Exception( "mix case" );
      }

      bool lower = bechString.ToLower() == bechString;

      bechString = bechString.ToLower();
      int pos = bechString.LastIndexOf( "1" );
      if (pos < 1 || pos + 7 > bechString.Length) {
        throw new Exception( "separator '1' at invalid position" );
      }

      string hrp = bechString.Substring( 0, pos );
      foreach (char c in hrp) {
        if (c < 33 || c > 126) {
          throw new Exception( "invalid character human-readable part" );
        }
      }
      List<int> data = new List<int>();
      for (int p = pos + 1; p < bechString.Length; p++) {
        int d = CHARSET.IndexOf( bechString[p] );
        if (d == -1) {
          throw new Exception( "invalid character data part" );
        }
        data.Add( d );
      }
      if (!VerifyChecksum( hrp, data.ToArray() )) {
        throw new Exception( "invalid checksum" );
      }
      if (!lower) {
        return (hrp.ToUpper(), data.Take( data.Count - 6 ).ToArray());
      }
      return (hrp, data.Take( data.Count - 6 ).ToArray());
    }
  }

CKBAddress

  public class CKBAddress {
    public static readonly string SecpCodeHash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8";
    public static readonly string SecpHashType = "type";
    static readonly int SecpShortId = 0;

    public static readonly string MultisigCodeHash = "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8";
    public static readonly string MultisigHashType = "type";
    static readonly int MultisigShortId = 1;

    public static string GenerateAddress(Script script, string prefix) {
      List<int> data = new List<int>();
      int? shortId = null;
      if (script.CodeHash == SecpCodeHash && script.HashType == SecpHashType) {
        shortId = SecpShortId;
      }
      else if (script.CodeHash == MultisigCodeHash && script.HashType == MultisigHashType) {
        shortId = MultisigShortId;
      }
      if (shortId != null) {
        data.Add( 1 );
        data.Add( (int)shortId );
        foreach (byte c in Convert.HexStringToBytes( script.Args )) {
          data.Add( System.Convert.ToInt32( c ) );
        }
      }
      else {
        data.Add( script.HashType == "type" ? 4 : 2 );
        foreach (byte c in Convert.HexStringToBytes( script.CodeHash )) {
          data.Add( System.Convert.ToInt32( c ) );
        }
        foreach (byte c in Convert.HexStringToBytes( script.Args )) {
          data.Add( System.Convert.ToInt32( c ) );
        }
      }
      string addr = ConvertAddress.Encode( prefix, data.ToArray() );

      return addr;
    }

    public static Script ParseShortAddress(string address, string prefix) {
      Bech32.Initialize( "bech32" );
      (string hrp, int[] data) = ConvertAddress.Decode( address );

      if (hrp != prefix) {
        throw new Exception( $"Invalid prefix! Expected: {prefix}, actual: {hrp}" );
      }
      // formatType = data[0]
      if (data[0] == 1) { // payload = 0x01 | code_hash_index | args
        if (data.Length < 2) {
          throw new Exception( "Invalid payload length!" );
        }
        string codeHash;
        string hashType;
        if (data[1] == SecpShortId) {
          codeHash = SecpCodeHash;
          hashType = SecpHashType;
        }
        else if (data[1] == MultisigShortId) {
          codeHash = MultisigCodeHash;
          hashType = MultisigHashType;
        }
        else {
          throw new Exception( "Short address format error!" );
        }

        return new Script() {
          CodeHash = codeHash,
          HashType = hashType,
          Args = IntsToHex( data.Skip( 2 ).ToArray() )
        };
      }
      // payload = 0x02 | codeHash | args
      // payload = 0x04 | codeHash | args
      else if (data[0] == 2 || data[0] == 4) {
        if (data.Length < 33) {
          throw new Exception( "Invalid payload length!" );
        }

        return new Script() {
          CodeHash = IntsToHex( data.Skip( 1 ).Take( 32 ).ToArray() ),
          HashType = data[0] == 2 ? "data" : "type",
          Args = IntsToHex( data.Skip( 33 ).ToArray() )
        };
      }
      throw new Exception( $"Invalid payload format type: {data[0]}" );
    }

    public static Script ParseAddress(string address, string prefix) {
      Bech32.Initialize( "bech32m" );
      (string hrp, int[] data) = ConvertAddress.Decode( address );

      if (hrp != prefix) {
        throw new Exception( $"Invalid prefix! Expected: {prefix}, actual: {hrp}" );
      }
      // formatType = data[0]
      // ADDRESS_FORMAT_FULL = 0x00;
      if (data[0] != 0)
        throw new Exception( "Invalid address format type" );
      if (data.Length < 34) {
        throw new Exception( "Invalid payload length, too short!" );
      }
      string codeHash = IntsToHex( data.Skip( 1 ).Take( 32 ).ToArray() );
      string hashType;
      int serializedHashType = data[1+32];
      if (serializedHashType == 0)
        hashType = "data";
      else if (serializedHashType == 1)
        hashType = "type";
      else if (serializedHashType == 2)
        hashType = "data1";
      else throw new Exception( "Invalid hashType " + serializedHashType );

      string args = IntsToHex( data.Skip( 34 ).ToArray() );
      return new Script() {
        CodeHash = codeHash,
        HashType = hashType,
        Args = args
      };
    }

    private static string IntsToHex(int[] values) {
      return Convert.BytesToHexString( values.Select( v => (byte)v ).ToArray() );
    }
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant