neodix42 / ton4j

Java libraries for interacting with TON blockchain.
GNU General Public License v3.0
78 stars 27 forks source link

Verifying signed in users on backend #23

Closed owtmeta closed 2 months ago

owtmeta commented 5 months ago

verify a user to provide them with their personal information from the back end.


https://docs.ton.org/develop/dapps/ton-connect/sign

Is there a plan to implement this?

Tks!

neodix42 commented 4 months ago

Thanks for leaving it here. This will be supported, but difficult to say when exactly.

fanfq commented 3 months ago
    public static void main(String[] args) throws Exception {

        String proof = "{\n" +
                "                \"timestamp\": 1722999580, \n" +
                "                \"domain\": {\n" +
                "                    \"lengthBytes\": 16, \n" +
                "                    \"value\": \"xxx.xxx.com\"\n" +
                "                }, \n" +
                "                \"signature\": \"i6dpzjKo/S+1xxx==\", \n" +
                "                \"payload\": \"doc-example-<BACKEND_AUTH_ID>\"\n" +
                "            }";

        String accountString = "{\n" +
                "        \"address\": \"0:307d63475bxxx", \n" +
                "        \"chain\": \"-239\", \n" +
                "        \"walletStateInit\": \"xxx=\", \n" +
                "        \"publicKey\": \"712d3b3d5xxxxxx\"\n" +
                "    }";

        TonProof tonProof = JSONObject.parseObject(proof,TonProof.class);
        WalletAccount account= JSONObject.parseObject(accountString,WalletAccount.class);

        log.info("tonProof:{}",JSONObject.toJSONString(tonProof));
        log.info("account:{}",JSONObject.toJSONString(account));

        boolean ret = checkProof(tonProof,account);
        log.info("ret:{}",ret);

    }

    public static boolean checkProof(TonProof tonProof, WalletAccount account) throws Exception {
        String address = account.getAddress().replace("0:", "");
        byte[] addressBytes = Hex.decodeHex(address);
        byte[] publicKeyBytes = Hex.decodeHex(account.getPublicKey());

        long timestamp = tonProof.getTimestamp();
        int domainLength = tonProof.getDomain().getLengthBytes();//.lengthBytes;
        String domainValue = tonProof.getDomain().getValue();//.value;
        byte[] signature = Base64.decode(tonProof.getSignature());
        String payload = tonProof.getPayload();

        // Create message
        byte[] h = "ton-proof-item-v2/".getBytes(StandardCharsets.UTF_8);
        ByteBuffer messageBuffer = ByteBuffer.allocate(h.length+ 4 + addressBytes.length + 4 + domainValue.length() + 8 + payload.length());
        messageBuffer.put(h);
        messageBuffer.putInt(Integer.reverseBytes(0)); // workchain, little-endian
        messageBuffer.put(addressBytes);
        messageBuffer.putInt(Integer.reverseBytes(domainLength)); // domain length, little-endian
        messageBuffer.put(domainValue.getBytes(StandardCharsets.UTF_8));
        messageBuffer.putLong(Long.reverseBytes(timestamp)); // timestamp, little-endian
        messageBuffer.put(payload.getBytes(StandardCharsets.UTF_8));

        byte[] message = messageBuffer.array();

        // Create message for signing
        MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
        byte[] hashedMessage = sha256.digest(message);
        byte[] signatureMessage = new byte[2 + "ton-connect".length() + hashedMessage.length];
        signatureMessage[0] = (byte) 0xFF;
        signatureMessage[1] = (byte) 0xFF;
        System.arraycopy("ton-connect".getBytes(StandardCharsets.UTF_8), 0, signatureMessage, 2, "ton-connect".length());
        System.arraycopy(hashedMessage, 0, signatureMessage, 2 + "ton-connect".length(), hashedMessage.length);

        byte[] hashedSignatureMessage = sha256.digest(signatureMessage);

        // Verify signature using Bouncy Castle
        Ed25519PublicKeyParameters publicKeyParameters = new Ed25519PublicKeyParameters(publicKeyBytes, 0);
        Ed25519Signer verifier = new Ed25519Signer();
        verifier.init(false, publicKeyParameters);
        verifier.update(hashedSignatureMessage, 0, hashedSignatureMessage.length);

        return verifier.verifySignature(signature);
    }

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

@Data
class TonProof {
    private long timestamp;
    private Domain domain;
    private String signature;
    private String payload;
}

@Data
class Domain {
    private int lengthBytes;
    private String value;
}

@Data
class WalletAccount {
    private String address;
    private String publicKey;
    private String chain;
    private String walletStateInit;
}
guoZhRoad commented 3 months ago
  public static void main(String[] args) throws Exception {

      String proof = "{\n" +
              "                \"timestamp\": 1722999580, \n" +
              "                \"domain\": {\n" +
              "                    \"lengthBytes\": 16, \n" +
              "                    \"value\": \"xxx.xxx.com\"\n" +
              "                }, \n" +
              "                \"signature\": \"i6dpzjKo/S+1xxx==\", \n" +
              "                \"payload\": \"doc-example-<BACKEND_AUTH_ID>\"\n" +
              "            }";

      String accountString = "{\n" +
              "        \"address\": \"0:307d63475bxxx", \n" +
              "        \"chain\": \"-239\", \n" +
              "        \"walletStateInit\": \"xxx=\", \n" +
              "        \"publicKey\": \"712d3b3d5xxxxxx\"\n" +
              "    }";

      TonProof tonProof = JSONObject.parseObject(proof,TonProof.class);
      WalletAccount account= JSONObject.parseObject(accountString,WalletAccount.class);

      log.info("tonProof:{}",JSONObject.toJSONString(tonProof));
      log.info("account:{}",JSONObject.toJSONString(account));

      boolean ret = checkProof(tonProof,account);
      log.info("ret:{}",ret);

  }

  public static boolean checkProof(TonProof tonProof, WalletAccount account) throws Exception {
      String address = account.getAddress().replace("0:", "");
      byte[] addressBytes = Hex.decodeHex(address);
      byte[] publicKeyBytes = Hex.decodeHex(account.getPublicKey());

      long timestamp = tonProof.getTimestamp();
      int domainLength = tonProof.getDomain().getLengthBytes();//.lengthBytes;
      String domainValue = tonProof.getDomain().getValue();//.value;
      byte[] signature = Base64.decode(tonProof.getSignature());
      String payload = tonProof.getPayload();

      // Create message
      byte[] h = "ton-proof-item-v2/".getBytes(StandardCharsets.UTF_8);
      ByteBuffer messageBuffer = ByteBuffer.allocate(h.length+ 4 + addressBytes.length + 4 + domainValue.length() + 8 + payload.length());
      messageBuffer.put(h);
      messageBuffer.putInt(Integer.reverseBytes(0)); // workchain, little-endian
      messageBuffer.put(addressBytes);
      messageBuffer.putInt(Integer.reverseBytes(domainLength)); // domain length, little-endian
      messageBuffer.put(domainValue.getBytes(StandardCharsets.UTF_8));
      messageBuffer.putLong(Long.reverseBytes(timestamp)); // timestamp, little-endian
      messageBuffer.put(payload.getBytes(StandardCharsets.UTF_8));

      byte[] message = messageBuffer.array();

      // Create message for signing
      MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
      byte[] hashedMessage = sha256.digest(message);
      byte[] signatureMessage = new byte[2 + "ton-connect".length() + hashedMessage.length];
      signatureMessage[0] = (byte) 0xFF;
      signatureMessage[1] = (byte) 0xFF;
      System.arraycopy("ton-connect".getBytes(StandardCharsets.UTF_8), 0, signatureMessage, 2, "ton-connect".length());
      System.arraycopy(hashedMessage, 0, signatureMessage, 2 + "ton-connect".length(), hashedMessage.length);

      byte[] hashedSignatureMessage = sha256.digest(signatureMessage);

      // Verify signature using Bouncy Castle
      Ed25519PublicKeyParameters publicKeyParameters = new Ed25519PublicKeyParameters(publicKeyBytes, 0);
      Ed25519Signer verifier = new Ed25519Signer();
      verifier.init(false, publicKeyParameters);
      verifier.update(hashedSignatureMessage, 0, hashedSignatureMessage.length);

      return verifier.verifySignature(signature);
  }

  static {
      Security.addProvider(new BouncyCastleProvider());
  }

@Data
class TonProof {
  private long timestamp;
  private Domain domain;
  private String signature;
  private String payload;
}

@Data
class Domain {
  private int lengthBytes;
  private String value;
}

@Data
class WalletAccount {
  private String address;
  private String publicKey;
  private String chain;
  private String walletStateInit;
}

Is there a specific implementation demo? Thank you

neodix42 commented 3 months ago

Thanks for the example. Let me review it and hopefully, I will add it very soon!

neodix42 commented 2 months ago

TonConnect has been added to the ton4j. See the latest release. Thanks for the code snippets!