[Webfunds-commits] java/webfunds/sox AbstractPayment.java RandomToken.java Token.java TokenPayment.java Value.java Encodable.java Payment.java PaymentFactory.java

Ian Grigg iang@cypherpunks.ai
Sat, 5 Aug 2000 09:05:40 -0400 (AST)


iang        00/08/05 09:05:40

  Modified:    webfunds/sox Encodable.java Payment.java PaymentFactory.java
  Added:       webfunds/sox AbstractPayment.java RandomToken.java
                        Token.java TokenPayment.java Value.java
  Log:
  1. Payment is now extension of AbstractPayment
  2. AbstractPayment should be base class of all Payments,
     but not clear yet entirely how successul this will be.
  3. Value is Interface that we should develop as a definable
     set of common services!
  4. Token is base for tokens a.k.a. coins.
  5. RandomToken is a class for big random numbers idea,
     can add WagnerToken, ChaumToken, BrandsToken at leisure.
  6. TokenPayment is early cut at holding RandomTokens.  Name
     should probably change to RandomPayment or somesuch.
  
  All are self-test Passes, and I can do ordinary Payments in WebFunds
  but watch out for payment bugs.
  
  Cost: 1 intensive day of coding!

Revision  Changes    Path
1.23      +5 -5      java/webfunds/sox/Encodable.java

Index: Encodable.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/Encodable.java,v
retrieving revision 1.22
retrieving revision 1.23
diff -u -r1.22 -r1.23
--- Encodable.java	2000/07/21 21:31:18	1.22
+++ Encodable.java	2000/08/05 13:05:39	1.23
@@ -1,5 +1,5 @@
 /*
- * $Id: Encodable.java,v 1.22 2000/07/21 21:31:18 gelderen Exp $
+ * $Id: Encodable.java,v 1.23 2000/08/05 13:05:39 iang Exp $
  *
  * Copyright (c) Systemics Ltd 1995-1999 on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
@@ -215,10 +215,10 @@
     }
 
     /**
-     * A utility function to write an X509 certificate
+     * A utility function to write a Certificate
      * to a DataOutputStream (with a length prefix).
      * Primarily provided since the encode() method of the
-     * X509Cert class insists on a stream parameter, and
+     * Certificate class insists on a stream parameter, and
      * does provide a simple method to return the object
      * encoded as a byte array.
      *
@@ -238,13 +238,13 @@
     }
 
     /**
-     * A utility function to read an X509 certificate
+     * A utility function to read a Certificate
      * from a DataInputStream (using a length prefix).
      * Primarily provided for completeness (to match
      * the writeCertificate).
      *
      * @param dis the stream from which the certificate is read
-     * @return An X509Cert object constructed from the input stream
+     * @return A Certificate object constructed from the input stream
      * @excep IOException An input error occurred whilst reading the stream
      */
     public static



1.22      +165 -147  java/webfunds/sox/Payment.java

Index: Payment.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/Payment.java,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -r1.21 -r1.22
--- Payment.java	2000/07/16 19:27:24	1.21
+++ Payment.java	2000/08/05 13:05:39	1.22
@@ -1,12 +1,18 @@
 /*
- * $Id: Payment.java,v 1.21 2000/07/16 19:27:24 iang Exp $
+ * $Id: Payment.java,v 1.22 2000/08/05 13:05:39 iang Exp $
  *
- * Copyright (c) Systemics Ltd 1995-1999 on behalf of
+ * Copyright (c) Systemics Inc 1995-2000 on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
 package webfunds.sox;
 
-import java.io.*;
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.OutputStream;
+import java.io.DataOutputStream;
+
 import java.security.PublicKey;
 
 /**
@@ -25,79 +31,96 @@
  * so developers of this class should be sure to cater
  * for derived classes.
  */
-public final class Payment extends Encodable
+    // this will become Abstract, later on when we add Payment
+public class Payment
+    extends AbstractPayment
 {
     /**
-     * The version number for this payment structure
+     *  The version number for this payment structure is 0 only.
+     *
+     *  As payments are signed, once signed they can never change.
      */
-    public static final int VERSION = 0;
-    protected int version = VERSION;
-  
+
     /**
      * The type of payment of this class
      * There is currently only one defined, but future payment
      * types may include things such as bearer payments, coins,
      * bank transfers and hash chain payments
+     */
+    public static final int TYPE = PaymentFactory.SOX_CHEQUE;
+
+    /**
+     *  The sub version of the original cheque payment.
+     *  The original is imputed as one (from VERSION==0), and
+     *  the newer subclass version as 1, implying a subclass.
+     *
+     *  Version 0 payments do not have a subversion.
      */
-    public static final int TYPE = 1;
-  
+    public static final int SUB_VERSION = 0;   // not used
+
+
+
     /**
      * The identifer for this payment
      */
     protected String paymentId;
-  
+
     /**
      * The account from which the payment is drawn
      */
     protected AccountId source;
-  
+
     /**
      * The account to which the payment is made out.
      */
     protected AccountId target;
-  
-    /**
-     * The type of item (or "currency") of this payment
-     * (e.g. airmiles)
-     */
-    protected ItemId item;
-  
-    /**
-     * The quantity of the item to be transferred between accounts
-     */
-    protected long qty;
-  
-    /**
-     * A description (can be anything, although very long lengths
-     * may be refused by some issuers and clients, but at least 2K
-     * is guaranteed to be supported by all SOX compatible software)
-     * Note that it can be non-ascii, and non readable.  That's up to
-     * the client software to deal with.
-     */
-    protected byte[] desc;
-  
+
+//    /**
+//     *  The type of item (or "currency") of this payment.
+//     *  E.g. "airmiles", "chips", ...
+//     *
+//     *  In pure SOX protocol form, this can be any byte array
+//     *  as encoded in ItemId.  In extant implementations, this
+//     *  will be the message digest of the Ricardian Contract.
+//     */
+//    protected ItemId item;
+//
+//    /**
+//     * The quantity of the item to be transferred between accounts
+//     */
+//    protected long qty;
+//
+//    /**
+//     * A description (can be anything, although very long lengths
+//     * may be refused by some issuers and clients, but at least 2K
+//     * is guaranteed to be supported by all SOX compatible software)
+//     * Note that it can be non-ascii, and non readable.  That's up to
+//     * the client software to deal with.
+//     */
+//    protected byte[] desc;
+
     /**
      * The date from which the payment is valid
      */
     protected long validFrom;
-  
+
     /**
      * The date to which the payment is valid
      */
     protected long validTill;
-  
+
     /**
      * The signature of the other payment fields
      * (made by the owner of the source account)
      */
     protected byte[] sig;
-  
+
     /**
      * this boolean indicated wether the account has to rollover to a new key.
      */
     protected boolean changekey;
-  
-  
+
+
     /**
      * Get the identifier on the payment, as originally created by the payer
      *
@@ -109,57 +132,57 @@
      *             -|:{}#
      */
     public String getId() { return paymentId; }
-  
+
     /**
      * Get the account from which the transaction is drawn
      */
     public AccountId getSource() { return source; }
-  
+
     /**
      * Get the account to which the payment is made
      */
     public AccountId getTarget() { return target; }
-  
+
     /**
      * @return true if the payment is made out to BEARER
      */
     public boolean isBearer() { return target.isBearer(); }
-  
-    /**
-     * Get the type of item (or "currency") of the payment
-     * (e.g. airmiles)
-     */
-    public ItemId getItem() { return item; }
-  
-    /**
-     * Get the quantity of the item for the payment
-     */
-    public long getQty() { return qty; }
-  
+
+//    /**
+//     * Get the type of item (or "currency") of the payment
+//     * (e.g. airmiles)
+//     */
+//    public ItemId getItem() { return item2; }
+//
+//    /**
+//     * Get the quantity of the item for the payment
+//     */
+//    public long getQty() { return qty; }
+
     /**
      * Get the date from which the payment is valid
      */
     public long getValidFrom() { return validFrom; }
-  
+
     /**
      * Get the date to which the payment is valid
      */
     public long getValidTill() { return validTill; }
-  
-    /**
-     * Get the description on the payment
-     */
-    public byte[] getDesc() { return desc; }
-  
-  
+
+//    /**
+//     * Get the description on the payment
+//     */
+//    public byte[] getDesc() { return desc; }
+
+
     /**
      * Get the signature of the payment
      */
     public byte[] getSignature() { return sig; }
-  
+
     public boolean getChangeKey() { return changekey; }
-  
-  
+
+
     /**
      * Create an unsigned payment
      *
@@ -180,13 +203,16 @@
                 byte[] desc, boolean changekey,
                 long validFrom, long validTill)
     {
+        super(SINGLETON_VERSION, TYPE, SUB_VERSION, item, qty, desc);
+
         this.paymentId = paymentId;
         this.source = source;
         this.target = target;
 
-        this.item = item;
-        this.qty = qty;
-        this.desc = desc;
+//        this.item = item;
+//        this.qty = qty;
+//        this.desc = desc;
+
         this.changekey = changekey;
 
         this.validFrom = validFrom;
@@ -196,24 +222,20 @@
     }
 
     /**
-     * Construct a payment object from a byte array
-     * that was previously returned from the encode()
-     * method of a payment object.
+     *  Construct a payment object from a byte array
+     *  that was previously returned from the encode()
+     *  method of a payment object.
      *
-     * If the signature is not present in the encoded data,
-     * the created payment will be unsigned.
+     *  If the signature is not present in the encoded data,
+     *  the created payment will be unsigned.
      *
-     * @param paymentData the previosly encoded payment
-     * @excep SOXPacketException The payment data is badly formatted
+     *  @param paymentData the previously encoded payment
+     *  @excep SOXPacketException The payment data is badly formatted
      */
     public Payment(byte[] paymentData)
         throws SOXPacketException
     {
-//        try {
-            decode(paymentData);
-//        } catch (IOException ex) {   // compiler doesn't insist, but thrown!
-//            throw new SOXPacketException("IOEx: " + ex);
-//        }
+        super(paymentData);
     }
 
     /**
@@ -225,17 +247,12 @@
      * the created payment will be unsigned.
      *
      * @param is the input stream from which to read the payment data
-     * @excep IOException An I/O exception has occurred
      * @excep SOXPacketException The payment data is badly formatted
      */
     public Payment(InputStream is)
         throws SOXPacketException
     {
-        try {
-            decode(is);
-        } catch (IOException ex) {   // compiler doesn't insist, but thrown!
-            throw new SOXPacketException("IOEx: " + ex);
-        }
+        super(is);
     }
 
     /**
@@ -258,12 +275,12 @@
     {
         this.sig = sig;
     }
-    
+
     /**
      * Verify the signature on the payment with the given public key
      * @param pk The publickey to verify the signature with
      * @return boolean true if payment is signed correctly
-     */    
+     */
     public boolean verify(PublicKey pk)
     {
         boolean retval;
@@ -285,8 +302,8 @@
         sig = tempsig;
         return retval;
     }
-    
 
+
     /**
      * Update this payment object with the values from
      * a payment encoded as a byte array (such as previously
@@ -303,14 +320,15 @@
         DataInputStream dis = new DataInputStream(is);
 
         int v = dis.readUnsignedByte();
-        if (v != VERSION)
+        if (v != SINGLETON_VERSION)
             throw new SOXPacketException("Invalid version in payment: "+
-                                         v+" != "+VERSION);
+                      v + " != " + SINGLETON_VERSION +
+                      "\n(V >= 1 can only be read by PacketFactory)");
         version = v;
 
         int type = dis.readUnsignedByte();
         if (type != TYPE)
-            throw new SOXPacketException("Invalid type of payment");
+            throw new SOXPacketException("Invalid type, not (SOX)Payment");
 
         paymentId = readString(dis);
         source = new AccountId(dis);
@@ -340,7 +358,7 @@
      * @return byte[] the payment in encoded form
      */
     public void encode(OutputStream os)
-    throws IOException
+        throws IOException
     {
         DataOutputStream dos = new DataOutputStream(os);
         dos.writeByte(version);
@@ -366,7 +384,7 @@
 
     public String toString()
     {
-        String retval = "SOX Payment V" + version + " T" + TYPE + ":";
+        String retval = "SOX Payment " + vString();
         retval += "  Q: "+qty;
         retval += "  PID: "+paymentId;
 
@@ -390,7 +408,7 @@
     }
 
     public boolean equals(java.lang.Object obj)
-    {                                             
+    {
         if (obj == null || !(obj instanceof Payment))
             return false;
 
@@ -404,7 +422,7 @@
             return false;
 
         ItemId it = other.getItem();
-        if (it == null) { 
+        if (it == null) {
             if (item != null)
                 return false ;
         } else {
@@ -413,7 +431,7 @@
         }
 
         AccountId ot = other.getSource();
-        if (ot == null) { 
+        if (ot == null) {
             if (source != null)
                 return false ;
         } else {
@@ -421,7 +439,7 @@
                 return false;
         }
         ot = other.getTarget();
-        if (ot.isBearer()) { 
+        if (ot.isBearer()) {
             if (!target.isBearer())
                 return false ;
         } else {
@@ -430,7 +448,7 @@
         }
 
         String his = other.getId();
-        if (his == null) { 
+        if (his == null) {
             if (paymentId != null)
                 return false ;
         } else {
@@ -442,9 +460,9 @@
         if (!Utils.byteEquals(desc, other.getDesc()))
             return false ;
 
-        return true; 
-    }         
-    
+        return true;
+    }
+
     private static final long YEAR = 365 * 24 * 60 * 60 * 1000;
     private static final long CENTURY = 100 * YEAR;
     private static final long NOWISH = 30 * YEAR;
@@ -455,8 +473,8 @@
         if (l < 0)
             l = -l;
         return l + NOWISH;
-    }         
-    
+    }
+
     public static Payment example()
     {
         ItemId item   = ItemId.example();
@@ -483,69 +501,69 @@
         Payment e;
         e = new Payment(pid, ac1, ac2, item, 10, desc, false, now, then);
         e.setSignature(sig);
-    
+
         return e;
     }
 
     public static void main(String[] args)
     {
-	int num = 20;
-	String type = "-c";
-	if (args.length > 0)
-	{
-	    int a = 0;
-	    if (args[a].startsWith("-"))
-	    {
-	        type = args[a++];
-	    }
+        int num = 2000;
+        String type = "-c";
+        if (args.length > 0)
+        {
+            int a = 0;
+            if (args[a].startsWith("-"))
+            {
+                type = args[a++];
+            }
 
             if (a < args.length)
-	    {
-		Integer i = null;
+            {
+                Integer i = null;
                 try {
-		    i = new Integer(args[a]);
-		} catch (Exception e) {
-		    e.printStackTrace();
-		    System.exit(1);
-		}
+                    i = new Integer(args[a]);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    System.exit(1);
+                }
                 num = i.intValue();
-	    }
-	}
+            }
+        }
 
         try {
-	    if (type.equals("-t") || type.equals("-i"))
-	    {
-		while (true)
-		{
+            if (type.equals("-t") || type.equals("-i"))
+            {
+                while (true)
+                {
                     if (type.equals("-t"))
-		        readWrite();
+                        readWrite();
                     else if (type.equals("-i"))
-		        input();
-	        }
-	    }
-	    else
-	    {
+                        input();
+                }
+            }
+            else
+            {
                 for (int i = 0; i < num; i++)
-	        {
+                {
                     if (type.equals("-c"))
-		        cycle();
+                        cycle();
                     else if (type.equals("-o"))
-		        output();
-	        }
-	    }
+                        output();
+                }
+            }
         } catch(Exception e) {
             e.printStackTrace();
             System.exit(1);
         }
-	System.out.flush();          // last buffered lump still there
-	System.out.close();
+        System.out.flush();          // last buffered lump still there
+        System.out.close();
         System.exit(0);
     }
 
     protected static void cycle()
         throws Exception
     {
-	Payment p = example();
+        Payment p = example();
         System.err.println("Writing: " + p);
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
@@ -562,24 +580,24 @@
     protected static void output()
         throws Exception
     {
-	Payment b = example();
-	b.encode(System.out);
+        Payment b = example();
+        b.encode(System.out);
     }
 
     protected static void readWrite()
         throws Exception
     {
-	Payment b = null;
-	    b = new Payment(System.in);
+        Payment b = null;
+            b = new Payment(System.in);
         System.err.println("Read: " + b);
-	b.encode(System.out);
+        b.encode(System.out);
     }
 
     protected static void input()
         throws Exception
     {
-	Payment b = null;
-	    b = new Payment(System.in);
+        Payment b = null;
+            b = new Payment(System.in);
         System.err.println("Read: " + b);
     }
 }



1.2       +12 -1     java/webfunds/sox/PaymentFactory.java

Index: PaymentFactory.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/PaymentFactory.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- PaymentFactory.java	2000/06/20 20:09:17	1.1
+++ PaymentFactory.java	2000/08/05 13:05:39	1.2
@@ -1,4 +1,4 @@
-/* $Id: PaymentFactory.java,v 1.1 2000/06/20 20:09:17 gelderen Exp $
+/* $Id: PaymentFactory.java,v 1.2 2000/08/05 13:05:39 iang Exp $
  *
  * Copyright (c) Systemics Inc. 1995-2000 on behalf of
  * The WebFunds Development Team. All Rights Reserved.
@@ -8,6 +8,17 @@
 
 public final class PaymentFactory
 {
+    /**
+     *  Use the numbers 100 - 110 for experimental methods.
+     *  Once you are ready, ask the WebFunds team to allocate
+     *  you a fixed number from below!
+     *  Hmm, that should keep contention in the other 4 billion
+     *  spots down to a workable minmum ;) 
+     */
+    public static final int        SOX_CHEQUE = 1,
+                                   RANDOM_TOKEN = 2,
+                                   WAGNER_TOKEN = 3;
+
     private PaymentFactory() {}
 
 



1.1                  java/webfunds/sox/AbstractPayment.java

Index: AbstractPayment.java
===================================================================
/*
 * $Id: AbstractPayment.java,v 1.1 2000/08/05 13:05:39 iang Exp $
 *
 * Copyright (c) Systemics Inc 1995-2000 on behalf of
 * the WebFunds Development Team.  All Rights Reserved.
 */
package webfunds.sox;

import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;

import java.security.PublicKey;

import webfunds.utils.Hex;

/**
 * This class represents a signed payment.
 * AbstractPayment objects are usually created using the
 * createPayment method of an Account object, or
 * re-constructed from a byte array which was
 * created using the encode() method of a previous
 * Payment object.  Payments are encoded as a byte
 * array for sending to third parties, and similarly,
 * byte arrays are the form in which payments are
 * received.
 *
 * It should be possible for classes to derive from
 * this one, in order to have different functionality,
 * so developers of this class should be sure to cater
 * for derived classes.
 */
    // this will become Abstract, later on when we add Payment
public abstract class AbstractPayment
    extends Encodable implements Value
{
    /**
     *  The version number for this payment structure:
     *         0:     sox 2 cheque only
     *         1:     AbstractPayment / PaymentFactory {cheque; token; wagner}
     */
    public static final int SINGLETON_VERSION = 0,   // sox 2 cheque only
                            PF_VERSION = 1;          // PaymentFactory

    protected int version;
    public int getVersion()               { return version; }

    /**
     *  The type of payment of this class.
     *  See PaymentFactory for types.
     */
    protected int type;
    public int getType()                  { return type; }

    /**
     *  The sub version of the subclass.
     */
    protected int subversion;
    public int getSubVersion()            { return subversion; }



    /**
     *  The type of item (or "currency") of this payment.
     *  E.g. "airmiles", "chips", ...
     *
     *  In pure SOX protocol form, this can be any byte array
     *  as encoded in ItemId.  In extant implementations, this
     *  will be the message digest of the Ricardian Contract.
     *
     *  In a SOX / practical context this turns out to be more or
     *  less mandatory, so included here?
     */
    protected ItemId item;
    public ItemId getItem() { return item; }

    /**
     *  The quantity of the item to be transferred between accounts
     */
    protected long qty;
    public long getQty() { return qty; }

    /**
     *  A description (can be anything, although very long lengths
     *  may be refused by some issuers and clients, but at least 2K
     *  is guaranteed to be supported by all SOX compatible software)
     *  Note that it can be non-ascii, and non readable.  That's up to
     *  the client software to deal with.
     *  Null is not actually supported, it will be promoted to an empty
     *  byte array.
     */
    protected byte[] desc;
    public byte[] getDesc() { return desc; }



    /**
     * Create a basic payment, a constructor for subclasses
     *
     * @param item the item for which the payment is for
     * @param desc a description of what this payment is for (optional)
     */
    public AbstractPayment(int version, int type, int subversion,
                           ItemId item, long qty, byte[] desc)
    {
        this.version    = version;
        this.type       = type;
        this.subversion = subversion;

        this.item = item;
        if (desc == null)
            desc = new byte[0];
        this.desc = desc;
        this.qty = qty;
    }

    /**
     *  Construct a payment object from a byte array
     *  that was previously returned from the encode()
     *  method of a payment object.
     *
     *  If the signature is not present in the encoded data,
     *  the created payment will be unsigned.
     *
     *  @param paymentData the previously encoded payment
     *  @excep SOXPacketException The payment data is badly formatted
     */
    public AbstractPayment(byte[] paymentData)
        throws SOXPacketException
    {
        decode(paymentData);
    }

    /**
     * Construct a payment object from data in an input stream,
     * where the data was previously returned from the encode()
     * method of a payment object.
     *
     * If the signature is not present in the encoded data,
     * the created payment will be unsigned.
     *
     * @param is the input stream from which to read the payment data
     * @excep SOXPacketException The payment data is badly formatted
     */
    public AbstractPayment(InputStream is)
        throws SOXPacketException
    {
        try {
            decode(is);
        } catch (IOException ex) {   // compiler doesn't insist, but thrown!
            throw new SOXPacketException("IOEx: " + ex);
        }
    }



    /**
     * Update this payment object with the values from
     * a payment encoded as a byte array (such as previously
     * returned from the encode() method of a payment object).
     *
     * If the signature is not present in the encoded data, the
     * current signature (if any) will be removed.
     *
     * @param paymentData the previosly encoded payment
     */
    public abstract void decode(InputStream is)
        throws IOException, SOXPacketException;

    /**
     * Encode a payment as a byte array, suitable for
     * sending to third parties for depositing.
     * If the signature is not present, an unsigned
     * payment will be encoded.
     *
     * @return byte[] the payment in encoded form
     */
    public abstract void encode(OutputStream os)
        throws IOException;


////// Self-Test //////////////////////////////////

    public String toString()
    {
        String s = "";     /* does not say what object, leave to subclass */
       
        s += "  Q: " + qty;
        s += "  of " + item.fp();
        if (desc != null && desc.length > 0)
            s += "  desc: " + Hex.printable(desc) +"\n";
      
        return s;
    }  
       
    public String vString()
    {  
        return "V" + subversion + " (" + version + ")";
    }

    public boolean equals(java.lang.Object obj)
    {
        if (obj == null || !(obj instanceof AbstractPayment))
            return false;
        
        AbstractPayment other = (AbstractPayment)obj;
        
        if (other.qty != qty)              
            return false;

        if (!Utils.byteEquals(desc, other.getDesc()))
            return false ;

        ItemId it = other.getItem();
        if (it == null) {
            if (item != null)
                return false ;
        } else {
            if (!it.equals(item))  
                return false;
        }

        return true;
    }

}



1.1                  java/webfunds/sox/RandomToken.java

Index: RandomToken.java
===================================================================
/*
 * $Id: RandomToken.java,v 1.1 2000/08/05 13:05:39 iang Exp $
 *
 * Copyright (c) Systemics Inc 1995-2000 on behalf of
 * the WebFunds Development Team.  All Rights Reserved.
 */
package webfunds.sox;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 *  This class represents a Big Random Number Token.
 *
 *  The token derives its security from the unguessability of
 *  the random number;  this is mostly useful for demonstration
 *  purposes than in actual live fire as its characteristics for
 *  payment systems use are dominated by other methods.
 *
 *  RandomToken objects are usually created by the mint.
 */
public class RandomToken
    extends Token
{
    /**
     *  The sub version number for this payment structure:
     *         0:     first cut.
     */
    public static final int SUB_VERSION = 0;

    /**
     * The type of token encoded by this class.
     */
    public static final int RANDOM_TYPE = 1;



    /**
     * Create a RandomToken
     *
     * @param byte[] random big random number (cannot be null)
     * @param qty is the amount (of unit of account) this token represents
     * @param expiry the Java time when this (batch) expires
     */
    public RandomToken(byte[] random, long qty, long expiry)
    {
        super(RANDOM_TYPE, SUB_VERSION, qty, expiry);

        setPayload(random);
        if (random.length > 0)
            setState(TOK_VALID);
    }

    public RandomToken(byte[] paymentData)
        throws SOXPacketException
    {
        super(paymentData);
    }

    public RandomToken(InputStream is)
        throws SOXPacketException
    {
        super(is);
    }


    /**
     * Update this payment object with the values from
     * a payment encoded as a byte array (such as previously
     * returned from the encode() method of a payment object).
     *
     * If the signature is not present in the encoded data, the
     * current signature (if any) will be removed.
     *
     * @param paymentData the previosly encoded payment
     */
    public void decode(InputStream is)
        throws IOException, SOXPacketException
    {
        super.decode(is);

        if (subversion != SUB_VERSION)
            throw new SOXPacketException("Invalid subversion in token: "+
                      subversion + " != " + SUB_VERSION);

        if (type != RANDOM_TYPE)
            throw new SOXPacketException("not RandomToken (" +subversion+ ")");

        /* nothing to add */
    }

    /**
     * Encode a payment as a byte array, suitable for
     * sending to third parties for depositing.
     * If the signature is not present, an unsigned
     * payment will be encoded.
     *
     * @return byte[] the payment in encoded form
     */
    public void encode(OutputStream os)
        throws IOException
    {
        super.encode(os);

        /* nothing to add */
    }



////// Self-Test //////////////////////////////////

    public String toString()
    {
        return "RandomToken " + vString() + " T" + type + ": " + super.toString();
    }

    public static RandomToken example()
    {
        byte b = Utils.exampleByte();
        long expiry = Utils.exampleLong();           // might be expired

        long qty  = Utils.exampleLong();
        if (qty < 0)
            qty = -qty;

        byte[] random;
        random = Utils.exampleData();

        RandomToken obj;
        obj = new RandomToken(random, expiry, qty);

        /* initialised in TOK_VALID */
        if ((b & 0x30) == 0x30)
            obj.setState(TOK_SPENT);
        else if ((b & 0xB0) == 0xB0)
            obj.setState(TOK_PROTO);

        return obj;
    }

    public static void main(String[] args)
    {
        int num = 2000;
        String type = "-c";
        if (args.length > 0)
        {
            int a = 0;
            if (args[a].startsWith("-"))
            {
                type = args[a++];
            }

            if (a < args.length)
            {
                Integer i = null;
                try {
                    i = new Integer(args[a]);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.exit(1);
                }
                num = i.intValue();
            }
        }

        try {
            if (type.equals("-t") || type.equals("-i"))
            {
                while (true)
                {
                    if (type.equals("-t"))
                        readWrite();
                    else if (type.equals("-i"))
                        input();
                }
            }
            else
            {
                for (int i = 0; i < num; i++)
                {
                    if (type.equals("-c"))
                        cycle();
                    else if (type.equals("-o"))
                        output();
                }
            }
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        System.out.flush();          // last buffered lump still there
        System.out.close();
        System.exit(0);
    }

    protected static void cycle()
        throws Exception
    {
        RandomToken p = example();
        System.err.println("Writing: " + p);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        p.encode(baos);
        byte[] buf = baos.toByteArray();

        RandomToken q = new RandomToken(buf);
        if (!p.equals(q))
        {
            throw new RuntimeException("FAILED:\n\n"+q+"\n\n"+p+"\nEND\n");
        }
    }

    protected static void output()
        throws Exception
    {
        RandomToken b = example();
        b.encode(System.out);
    }

    protected static void readWrite()
        throws Exception
    {
        RandomToken b = null;
            b = new RandomToken(System.in);
        System.err.println("Read: " + b);
        b.encode(System.out);
    }

    protected static void input()
        throws Exception
    {
        RandomToken b = null;
            b = new RandomToken(System.in);
        System.err.println("Read: " + b);
    }
}



1.1                  java/webfunds/sox/Token.java

Index: Token.java
===================================================================
/*
 * $Id: Token.java,v 1.1 2000/08/05 13:05:39 iang Exp $
 *
 * Copyright (c) Systemics Inc 1995-2000 on behalf of
 * the WebFunds Development Team.  All Rights Reserved.
 */
package webfunds.sox;

import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;

// import java.security.PublicKey;

import webfunds.utils.Hex;

/**
 *  This class represents a token (a.k.a. coin).
 *  Token objects are usually created by means of a Withdrawal
 *  protocol between client and mint-capable SOXServer.
 *  They might go through several phases, being potentially:
 *            proto
 *            signed
 *            blinded
 *            spent
 *  which may or may not be exclusive.
 *
 */
public abstract class Token
    extends Encodable
{
    /**
     *  The version number for this structure:
     *         0:     current
     */
    public static final int VERSION = 0;
    protected int version = VERSION;
    public final int getVersion()               { return version; }

    /**
     * The type of token of this class
     * See TokenFactory for current definitions.
     */
    protected int type;
    public int getType()                        { return type; }

    /**
     *  The version of the subclass Token.
     *
     */
    protected int subversion;
    public int getSubVersion()                  { return subversion; }



    /**
     *  The state that the token is in
     *  (generally made by the mint according to the protocol).
     *  How it is set and advanced is up to the higher layers.
     *  May be ignored, may use the following TOK numbers.
     *  Only the low-order single unsigned byte is saved & restored.
     */
    protected int state;
    public int getState()                       { return state; }
    public void setState(int state)             { this.state = state; }

    public static final int            TOK_PROTO  = 0,
                                       TOK_VALID  = 1,
                                       TOK_SIGNED = 2,
                                       TOK_BLIND  = 3,
                                       TOK_SPENT  = 4;

    /**
     *  The quantity of the item that this token represents.
     */
    protected long qty;
    /**
     *  Note (1) that this may be derived information, the real
     *  token value may be encoded in the signature key or
     *  some other way.
     *
     *  Note (2) that the unit of account is not encoded within
     *  the token, the higher layer code should remember that.
     *  See Payments for the encoded Item (a.k.a. Ricardian Contract).
     *
     *  @return quantity of the item that this token represents.
     */
    public long getQty()                        { return qty; }

    /**
     *  Tokens commonly come from a batch with a defined expiry date.
     *  This is interpreted by the subclass, may be ignored.
     */
    protected long expiry;
    public long getExpiry()                     { return expiry; }

    /**
     *  The token payload
     *  (generally made by the mint according to the protocol).
     *  How it is made and verified is up to the higher layers.
     *  May be ignored (for technical reasons, cannot be null).
     */
    protected byte[] token;
    /**
     *  @return the token payload (never null)
     */
    public byte[] getPayload()                  { return token; }
    /**
     *  @param a new payload for this token (null becomes empty array)
     */
    public void   setPayload(byte[] b)
    {
        if (b == null)
            b = new byte[0];
        this.token = b;
    }

    /**
     *  The signature on the token
     *  (generally made by the mint according to the protocol).
     *  How it is made and verified is up to the higher layers.
     *  May be ignored (for technical reasons, cannot be null).
     */
    protected byte[] sig;
    /**
     *  @return the signature on the token (never null)
     */
    public byte[] getSignature()                { return sig; }
    /**
     *  @param a new signature on this token (null becomes empty array)
     */
    public void   setSignature(byte[] sig)
    {
        if (sig == null)
            sig = new byte[0];
        this.sig = sig;
    }
    /**
     * Check to see if this token is signed.
     * NOTE: This does not check the validity of the signature,
     * only that this token has one.
     * @return boolean true if this token has a signature
     */
    public boolean isSigned()                   { return (sig.length > 0); }



    /**
     *  Create an unsigned token.
     *
     *  @param desc a description of what this token is for (optional)
     *  @param qty the number of items of which the token is for
     *  @param expiry the Java time till which the token is valid
     */
    public Token(int type, int subversion, long qty, long expiry)
    {
        this.type       = type;
        this.subversion = subversion;
        this.qty        = qty;
        this.expiry     = expiry;

        this.state = TOK_PROTO;

        this.sig = this.token = new byte[0];
    }

    /**
     *  Construct a token object from a byte array
     *  that was previously returned from the encode()
     *  method of a token object.
     *
     *  @param token the previously encoded token
     *  @excep SOXPacketException The token data is badly formatted
     */
    public Token(byte[] token)
        throws SOXPacketException
    {
        decode(token);
    }

    /**
     *  Construct a token object from data in an input stream,
     *  where the data was previously returned from the encode()
     *  method of a token object.
     *
     *  @param is the input stream from which to read the token data
     *  @excep SOXPacketException The token data is badly formatted
     */
    public Token(InputStream is)
        throws SOXPacketException
    {
        try {
            decode(is);
        } catch (IOException ex) {   // compiler doesn't insist, but thrown!
            throw new SOXPacketException("IOEx: " + ex);
        }
    }

    /**
     *  Verify the signature on the token with the given public key
     *  @param pk The publickey to verify the signature with
     *  @return this super-method always returns false, subclass should replace
     * 
    public boolean verify(PublicKey pk)             { return false; }
     */


    /**
     *  Update this token object with the values from
     *  a token encoded as a byte array (such as previously
     *  returned from the encode() method of a token object).
     *
     *  @param token the previosly encoded token
     */
    public void decode(InputStream is)
        throws IOException, SOXPacketException
    {
        DataInputStream dis = new DataInputStream(is);

        int v = dis.readUnsignedByte();
        if (v != VERSION)
            throw new SOXPacketException("Invalid version in token: "+
                                         v + " != " + VERSION);
        version = v;

        /*
         *  Type and subversion should be interpreted by subclass.
         */
        type = dis.readUnsignedByte();
        subversion = dis.readUnsignedByte();


        state = dis.readUnsignedByte();
        qty = dis.readLong();
        expiry = dis.readLong();

        sig = readByteArray(dis);          // always returns array
        token = readByteArray(dis);

    }

    /**
     * Encode a token as a byte array, suitable for
     * sending to third parties for depositing.
     * If the signature is not present, an unsigned
     * token will be encoded.
     *
     * @return byte[] the token in encoded form
     */
    public void encode(OutputStream os)
        throws IOException
    {
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeByte(version);
        dos.writeByte(type);
        dos.writeByte(subversion);

        dos.writeByte(state);
        dos.writeLong(qty);
        dos.writeLong(expiry);

        writeByteArray(dos, sig);
        writeByteArray(dos, token);
    }



////// Self-Test //////////////////////////////////

    public String toString()
    {
        String s = "";     /* does not say what object, leave to subclass */

        s += "  Q: " + qty;
        s += "  exp: " + expiry + "\n";
        s += "  sig: " + Hex.data2hex(sig) +"\n";
        s += "  tok: " + Hex.data2hex(token) +"\n";

        return s;
    }

    public String vString()
    {
        return "V" + subversion + " (" + version + ")";
    }

    public boolean equals(java.lang.Object obj)
    {
        if (obj == null || !(obj instanceof Token))
            return false;

        Token other = (Token)obj;

        if (other.state != state)
            return false;
        if (other.qty != qty)
            return false;
        if (other.expiry != expiry)
            return false;

        if (!Utils.byteEquals(sig, other.getSignature()))
            return false ;
        if (!Utils.byteEquals(token, other.getPayload()))
            return false ;

        return true;
    }

    /* public abstract static Token example(); */

}



1.1                  java/webfunds/sox/TokenPayment.java

Index: TokenPayment.java
===================================================================
/*
 * $Id: TokenPayment.java,v 1.1 2000/08/05 13:05:39 iang Exp $
 *
 * Copyright (c) Systemics Inc 1995-2000 on behalf of
 * the WebFunds Development Team.  All Rights Reserved.
 */
package webfunds.sox;

// import java.io.*;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;

import webfunds.utils.Panic;

/**
 *  This class represents a token payment.
 *  A token payment consists of tokens, sometimes
 *  called coins, which at this level are simply
 *  big unguessable numbers.
 *
 *  These are created by collecting the tokens out
 *  of a database, or by acquiring them from a mint-
 *  enabled Issuer with a WithdrawalRequest.
 *
 *  It may be useful for classes to derive from
 *  this one, in order to have different functionality.
 *  For example, it may be easier to derive from this
 *  class for the various blinded methods than to have
 *  another class.
 */
public class TokenPayment
    extends AbstractPayment
{
    /**
     *  The type of payment of this class.
     *  See the PaymentFactory for the other types.
     */
    public static final int TOKEN_TYPE = PaymentFactory.RANDOM_TOKEN;
    public int getType()                  { return TOKEN_TYPE; }

    /**
     *  The version number for this payment structure
     *  (*NOT* the super packet type number).
     */
    public static final int TOKEN_VERSION = 0;

    /**
     *  The account from which the payment was drawn.
     *  This is meaningless in a token concept, just here for
     *  informational purposes.
     */
    protected AccountId source;
    /** Get the account from which the transaction is drawn */
    public AccountId getSource() { return source; }

    /**
     *  The account to which the payment is made out.
     *  This is meaningless in a token concept, just here for
     *  informational purposes.
     */
    public AccountId getTarget() { return new AccountId(); }
    /** @return true if the payment is made out to BEARER (always true) */
    public boolean isBearer() { return true; }

    /**
     *  List of Tokens.
     */
    protected Token[] tokens;
    /** Get the tokens within the payment */
    public Token[] getTokens() { return tokens; }



    /**
     *  Create a token payment from an array of tokens.
     *
     *  An empty array can be passed to simulate a zero payment,
     *  in the time-honoured SOX tradition.  We may want to support
     *  Zero coins, dunno.
     *
     *  The items is indicated so that a pointer to a valid SOXServer
     *  can be determined at the receiving end.
     *
     *  @param source the source account, can be empty / null
     *  @param item the item for which the payment is for
     *  @param desc a description of what this payment is for (null ==> empty)
     *  @param tokens an array of tokens, cannot be null or contain nulls,
     *         may be empty
     */
    public TokenPayment(
                AccountId source,
                ItemId item,
                byte[] desc,
                Token[] tokens
                )
    {
        super(PF_VERSION, TOKEN_TYPE, TOKEN_VERSION, item, 0, desc);

        if (item == null)
            throw new Panic("item is null, must supply valid item");

        if (source == null)
            source = new AccountId();
        this.source = source;

        this.tokens = tokens;

        if (tokens == null)
            throw new Panic("tokens array cannot be null (but may be empty)");
        if (tokens.length > 0x00FF)
            throw new Panic("tokens array too long (unsigned byte length)");

        qty = 0;
        for (int i = 0; i < tokens.length; i++)
        {
            Token tok = tokens[i];
            if (tok == null)
                throw new Panic("null token in array at position " + i);
            qty += tokens[i].getQty();
        }
    }

    /**
     *  Construct a payment object from a byte array that was previously
     *  returned from the encode() method of a payment object.
     *
     *  @param paymentData the previosly encoded payment
     *  @excep SOXPacketException The payment data is badly formatted
     */
    public TokenPayment(byte[] paymentData)
        throws SOXPacketException
    {
        super(paymentData);
    }

    /**
     *  Construct a payment object an input stream, where the data was
     *  previously returned from the encode() method of a payment object.
     *
     *  @param is the input stream from which to read the payment data
     *  @excep SOXPacketException The payment data is badly formatted
     */
    public TokenPayment(InputStream is)
        throws SOXPacketException
    {
        super(is);
    }

    /**
     *  Check to see if this payment is signed, always false on a token
     *  payment?  This may not be the case, the tokens themselves may
     *  may be signed.
     *
     *  @return boolean false (no signature on a token payment)
     */
    public boolean isSigned()          { return false; }



    /**
     *  Update this payment object with the values from
     *  a payment encoded as a byte array (such as previously
     *  returned from the encode() method of a payment object).
     *
     *  @param paymentData the previously encoded payment
     */
    public void decode(InputStream is)
        throws IOException, SOXPacketException
    {
        DataInputStream dis = new DataInputStream(is);

        int v = dis.readUnsignedByte();
        if (v != PF_VERSION)
            throw new SOXPacketException("Invalid super version in TP: " +
                                         v + " != " + PF_VERSION);
        version = v;

        int type = dis.readUnsignedByte();
        if (type != TOKEN_TYPE)
            throw new SOXPacketException("Invalid type of payment, " +
                                         type + " != " + TOKEN_TYPE);

        /*
         * For token payments (and the PaymentFactory regime)
         * a new packet type is added here for the subpacket.
         */
        v = dis.readUnsignedByte();
        if (v != getSubVersion())
            throw new SOXPacketException("Invalid version in payment: " +
                                         v + " != " + getSubVersion());
        subversion = v;

        source = new AccountId(dis);     // may be empty
        qty = dis.readLong();
        item = new ItemId(dis);
        desc = readByteArray(dis);

        int number = dis.readUnsignedByte();

        if (number < 0)
            throw new SOXPacketException("Negative number of tokens");

        tokens = (Token[]) new RandomToken[number];
        for (int i = 0; i < number; i++)
             tokens[i] = new RandomToken(dis);
    }

    /**
     * Encode a payment as a byte array, suitable for
     * sending to third parties for depositing.
     * If the signature is not present, an unsigned
     * payment will be encoded.
     *
     * @return byte[] the payment in encoded form
     */
    public void encode(OutputStream os)
        throws IOException
    {
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeByte(getVersion());
        dos.writeByte(getType());
        dos.writeByte(getSubVersion());

        source.encode(dos);
        dos.writeLong(qty);
        item.encode(dos);
        writeByteArray(dos, desc);

        int number = tokens.length;
        dos.writeByte(number);
        for (int i = 0; i < number; i++)
             tokens[i].encode(dos);
    }



////// Self-Test //////////////////////////////////

    public String toString()
    {
        String s = "TokenPayment " + vString() + "  " + super.toString();

        s += "  " + tokens.length + " tokens";

        if (!source.isBearer())
            s += "\n   from: " + source.fp();

        return s;
    }

    public boolean equals(java.lang.Object obj)
    {
        if (obj == null || !(obj instanceof TokenPayment))
            return false;
//System.err.println("        1");

        if (!super.equals((AbstractPayment)obj))
            return false;

//System.err.println("        3");
        TokenPayment other = (TokenPayment)obj;

//System.err.println("        5");
        AccountId ot = other.getSource();
        if (ot == null) {
            if (source != null)
                return false ;
        } else {
            if (!ot.equals(source))
                return false;
        }

//System.err.println("        7");
        Token[] his = other.getTokens();
        if (his.length != tokens.length)
            return false;
        for (int i = 0; i < tokens.length; i++)
        {
            if (!tokens[i].equals(his[i]))
                return false ;
        }
//System.err.println("        9");

        return true;
    }

    public static TokenPayment example()
    {
        int b = Utils.exampleByte();
        byte[] desc = Utils.exampleData();
        if ((b & 0xF0) == 0xF0)
            desc = null;

        AccountId ac = AccountId.example();
        if ((b & 0xD0) == 0xD0)
            ac = null;

        ItemId item = ItemId.example();

        int i = b & 0x0F;      // up to 16 tokens
        Token[] tokens = new Token[i];
        while (i-- > 0)
            tokens[i] = RandomToken.example();

        TokenPayment obj;
        obj = new TokenPayment(ac, item, desc, tokens);

        return obj;
    }

    public static void main(String[] args)
    {
        int num = 2000;
        String type = "-c";
        if (args.length > 0)
        {
            int a = 0;
            if (args[a].startsWith("-"))
            {
                type = args[a++];
            }

            if (a < args.length)
            {
                Integer i = null;
                try {
                    i = new Integer(args[a]);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.exit(1);
                }
                num = i.intValue();
            }
        }

        try {
            if (type.equals("-t") || type.equals("-i"))
            {
                while (true)
                {
                    if (type.equals("-t"))
                        readWrite();
                    else if (type.equals("-i"))
                        input();
                }
            }
            else
            {
                for (int i = 0; i < num; i++)
                {
                    if (type.equals("-c"))
                        cycle();
                    else if (type.equals("-o"))
                        output();
                }
            }
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        System.out.flush();          // last buffered lump still there
        System.out.close();
        System.exit(0);
    }

    protected static void cycle()
        throws Exception
    {
        TokenPayment p = example();
        System.err.println("Writing: " + p);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        p.encode(baos);
        byte[] buf = baos.toByteArray();

        TokenPayment q = new TokenPayment(buf);
        if (!p.equals(q))
        {
            throw new RuntimeException("FAILED:\n\n"+q+"\n\n"+p+"\nEND\n");
        }
    }

    protected static void output()
        throws Exception
    {
        TokenPayment b = example();
        b.encode(System.out);
    }

    protected static void readWrite()
        throws Exception
    {
        TokenPayment b = null;
            b = new TokenPayment(System.in);
        System.err.println("Read: " + b);
        b.encode(System.out);
    }

    protected static void input()
        throws Exception
    {
        TokenPayment b = null;
            b = new TokenPayment(System.in);
        System.err.println("Read: " + b);
    }
}



1.1                  java/webfunds/sox/Value.java

Index: Value.java
===================================================================
/*
 * $Id: Value.java,v 1.1 2000/08/05 13:05:39 iang Exp $
 *
 * Copyright (c) Systemics Inc 1995-2000 on behalf of
 * the WebFunds Development Team.  All Rights Reserved.
 */
package webfunds.sox;

import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;

import java.security.PublicKey;

/**
 * This class describes a payment object.
 */
public interface Value
{
    /**
     *  The version number for the encoded form.
     *         0:     sox 2 cheque only
     *         1:     AbstractPayment / PaymentFactory {cheque; token; wagner}
     */
    public int getVersion();

    /**
     *  The type of payment of this class.
     *  See PaymentFactory for types.
     */
    public int getType();

    /**
     *  The sub version of the subclass.
     */
    public int getSubVersion();



    /**
     *  The type of item (or "currency") of this payment.
     *  E.g. "airmiles", "chips", ...
     *
     *  In pure SOX protocol form, this can be any byte array
     *  as encoded in ItemId.  In extant implementations, this
     *  will be the message digest of the Ricardian Contract.
     *
     *  In a SOX / practical context this turns out to be more or
     *  less mandatory, so included here?
     */
    public ItemId getItem() ;

    /**
     *  The quantity of the item to be transferred between accounts
     */
    public long getQty() ;

    /**
     *  A description (can be anything, although very long lengths
     *  may be refused by some issuers and clients, but at least 2K
     *  is guaranteed to be supported by all SOX compatible software)
     *  Note that it can be non-ascii, and non readable.  That's up to
     *  the client software to deal with.
     */
    public byte[] getDesc() ;

}